Skip to content

Conversation

@khushiiagrawal
Copy link
Contributor

@khushiiagrawal khushiiagrawal commented Jan 1, 2026

Summary

Fixes two issues with inheritance documentation:

  1. Uses Sphinx RST syntax :py:class:BaseClass`` instead of plain text for inheritance links
  2. Groups inherited methods separately and adds "Inherited from :py:class:BaseClass." notation

Fixes #180

Changes

  • CodeGenerator.py: Generate Sphinx RST syntax in class docstrings; group inherited methods after class-defined methods
  • PXDParser.py: Track inherited methods with base class names
  • DeclResolver.py: Pass base class name when attaching inherited methods
  • test_full_library.py: Update expected docstring length (93→101 chars)

Testing

  • All existing tests pass
  • Verified Sphinx RST syntax appears in class docstrings
  • Verified inherited methods are grouped after class-defined methods
  • Verified "Inherited from" notation appears in .pyi file docstrings

Test case: Bklass inheriting from A_second - inherits callA2() method

Summary by CodeRabbit

  • New Features

    • Class docstrings now show Sphinx-style "Inherited from" links and record inheritance source.
    • Added a script to generate a self-contained Sphinx demo illustrating inheritance docs.
  • Improvements

    • Methods listed by origin: class-defined first, inherited after; constructors preserved.
    • Inheritance source is tracked and propagated to runtime wrappers and type stubs.
    • getitem bounds checks are applied only for integral index types.
  • Tests

    • Updated test expectations for docstring formatting/length.

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

Signed-off-by: Khushi Agrawal <149886195+khushiiagrawal@users.noreply.github.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 1, 2026

📝 Walkthrough

Walkthrough

Inheritance origins are recorded during PXD parsing and resolution, propagated as inherited_from through wrapper and stub generation, rendered in class docstrings using Sphinx :py:class: links, grouped/annotated on methods, getitem/setitem handling refined for integral types, and a new script automates a Sphinx demo build.

Changes

Cohort / File(s) Change summary
PXD parsing / AST
autowrap/PXDParser.py
Add inherited_method_bases to CppClassDecl; change attach_base_methods(..., base_class_name=None) to record originating base for each attached method.
Declaration resolution
autowrap/DeclResolver.py
Pass base_class_name=super_cld.name when attaching inherited methods; update ResolvedClass.attach_base_methods(self, methods, base_class_name).
Code generation
autowrap/CodeGenerator.py
Thread inherited_from through wrapper/stub APIs (_create_overloaded_method_decl, create_wrapper_for_method, create_wrapper_for_nonoverloaded_method, _create_fun_decl_and_input_conversion); render Sphinx :py:class: links in class docstrings; separate class-defined vs inherited methods (preserve ctor order); annotate runtime wrappers and .pyi stubs with inherited-from info; add _is_integral_type and refine getitem/setitem bounds handling.
Tests
tests/test_full_library.py, tests/test_code_generator_libcpp.py
Update expected docstring lengths to account for Sphinx :py:class: markup (adjust assertions and explanatory comments).
Tooling / Demo
create_sphinx_demo.py
Add end-to-end Sphinx demo script with run_command(cmd, cwd=None, check=True) and main() to run autowrap, build the Cython extension, configure Sphinx, generate docs, and open the result.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant P as PXDParser
  participant R as DeclResolver
  participant C as CodeGenerator
  participant F as Files/Tests
  participant U as User

  Note over P: Parse .pxd → CppClassDecls\n(record base classes)
  P->>R: attach_base_methods(transformed_methods,\n base_class_name)
  Note right of R: Record `inherited_method_bases` per method
  R->>C: provide resolved class decls (include inherited_method_bases)
  Note left of C: Thread `inherited_from` into wrapper & stub creation
  C->>F: write runtime wrappers & `.pyi` stubs (annotated)
  F->>U: tests/docs verify :py:class: links and inherited annotations
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I'm a rabbit with a quill and a hop,
I stitched base names from bottom to top.
Docs now link where methods came from,
Stubs wear tiny tags: "inherited_from."
🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: fixing inheritance documentation by using Sphinx RST syntax and grouping methods appropriately.
Linked Issues check ✅ Passed The PR implements all requirements from issue #180: Sphinx :py:class: syntax for inheritance links, grouped inherited methods with 'Inherited from' annotations, and consistent method ordering.
Out of Scope Changes check ✅ Passed All changes are directly related to inheritance documentation improvements. The new create_sphinx_demo.py is a demonstration/verification script for the feature, which is appropriate for this PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

@khushiiagrawal
Copy link
Contributor Author

@jpfeuffer Please take a look.
Thanks!

@jpfeuffer
Copy link
Contributor

Looks good but to be sure, we would need to see a sphinx documentation page with autodoc for this.

Signed-off-by: Khushi Agrawal <149886195+khushiiagrawal@users.noreply.github.com>
Copy link
Contributor

@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: 2

🧹 Nitpick comments (4)
create_sphinx_demo.py (4)

50-53: Consider using unpacking for more idiomatic list construction.

The list concatenation works correctly, but using the unpacking operator * is more idiomatic in modern Python.

🔎 Proposed refactor
     run_command([
         "autowrap",
-        "--out", "moduleB.pyx"
-    ] + pxd_files)
+        "--out", "moduleB.pyx",
+        *pxd_files
+    ])

122-139: Simplify the conf.py modification logic.

The logic to insert sys.path.insert has several issues:

  1. Redundant check at line 130 (already checked at line 122)
  2. The variable new_lines may not be defined if the condition at line 129 never matches
  3. The isinstance(new_lines, list) check at line 139 is unnecessary since new_lines is always a list when defined
🔎 Proposed refactor for clearer logic
         # Add path configuration if not already present
         if "sys.path.insert" not in conf_content:
-            # Find the extensions line and add after it
-            lines = conf_content.split("\n")
-            new_lines = []
-            for i, line in enumerate(lines):
-                new_lines.append(line)
-                # Add after extensions or after imports
-                if "extensions = [" in line or (i > 0 and "import sys" in lines[i-1] and "import os" in line):
-                    if "sys.path.insert" not in conf_content:
-                        new_lines.append("")
-                        new_lines.append("# Add current directory to path for autodoc")
-                        new_lines.append("import sys")
-                        new_lines.append("import os")
-                        new_lines.append("sys.path.insert(0, os.path.abspath('.'))")
-                        break
-        
-        with open(conf_py_path, "w") as f:
-            f.write("\n".join(new_lines) if isinstance(new_lines, list) else conf_content)
+            # Prepend the path configuration at the top
+            path_setup = """import sys
+import os
+
+# Add current directory to path for autodoc
+sys.path.insert(0, os.path.abspath('.'))
+
+"""
+            conf_content = path_setup + conf_content
+        
+        with open(conf_py_path, "w") as f:
+            f.write(conf_content)

226-231: Remove unnecessary f-string prefixes.

Lines 226 and 228 use f-strings without any placeholders. Regular strings would be clearer.

🔎 Proposed refactor
-    print(f"\nHTML documentation is available at:")
+    print("\nHTML documentation is available at:")
     print(f"  {html_path}")
-    print(f"\nTo view it, run:")
+    print("\nTo view it, run:")
     print(f"  open {html_path}  # macOS")

234-240: Consider catching more specific exceptions.

Catching bare Exception is overly broad. For better error handling, catch specific exceptions that webbrowser.open() might raise, such as OSError or webbrowser.Error.

🔎 Proposed refactor
     # Try to open automatically
     try:
         import webbrowser
         webbrowser.open(f"file://{html_path}")
         print("\nOpened in default browser!")
-    except Exception as e:
+    except (OSError, webbrowser.Error) as e:
         print(f"\nCould not open browser automatically: {e}")
         print("Please open the file manually.")
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d492ada and 8b28f26.

📒 Files selected for processing (1)
  • create_sphinx_demo.py
🧰 Additional context used
🧬 Code graph analysis (1)
create_sphinx_demo.py (1)
tests/test_files/templated.hpp (1)
  • f (61-64)
🪛 Ruff (0.14.10)
create_sphinx_demo.py

13-13: subprocess call: check for execution of untrusted input

(S603)


50-53: Consider ["autowrap", "--out", "moduleB.pyx", *pxd_files] instead of concatenation

Replace with ["autowrap", "--out", "moduleB.pyx", *pxd_files]

(RUF005)


104-104: Local variable quickstart_input is assigned to but never used

Remove assignment to unused variable quickstart_input

(F841)


226-226: f-string without any placeholders

Remove extraneous f prefix

(F541)


228-228: f-string without any placeholders

Remove extraneous f prefix

(F541)


238-238: Do not catch blind exception: Exception

(BLE001)

🔇 Additional comments (1)
create_sphinx_demo.py (1)

10-25: No security concern—all run_command calls already use list-based commands.

All four call sites of run_command (lines 50, 88, 105, and 219) pass list-based commands, which means shell=False is always used. The hard-coded commands reference only known tools (autowrap, sphinx-quickstart, sphinx-build, python) and project paths, with no user input interpolation. This script follows the recommended best practice and has no security risk.

@khushiiagrawal
Copy link
Contributor Author

khushiiagrawal commented Jan 1, 2026

Looks good but to be sure, we would need to see a sphinx documentation page with autodoc for this.

@jpfeuffer I've generated Sphinx documentation demonstrating the inheritance fixes. Here's what the screenshot shows:

Screenshot 2026-01-01 at 11 37 31 PM Screenshot 2026-01-01 at 11 37 45 PM

Inheritance link in class docstring: "— Inherits from A_second" - A_second is a clickable cross-reference
Inherited method notation: "Inherited from A_second." - A_second is a clickable link
Method grouping: Class-defined methods appear first, inherited methods after

The HTML file is attached, and you can regenerate it using python create_sphinx_demo.py (script included in this PR). The Sphinx RST syntax works correctly and creates functional cross-references.

@jpfeuffer
Copy link
Contributor

jpfeuffer commented Jan 1, 2026

Looks pretty good. Can you demonstrate the method grouping. I only see one method.
Also, there seems to be a broken link in your second picture.
I wonder if we should put inherited methods first or last?

Signed-off-by: Khushi Agrawal <149886195+khushiiagrawal@users.noreply.github.com>
@khushiiagrawal
Copy link
Contributor Author

@jpfeuffer Thank you for the feedback! I've investigated and fixed the issues:

1. Method Grouping

I found and fixed a bug in CodeGenerator.py where only the last inherited method was being generated due to an indentation error. Now all inherited methods are properly generated and grouped.

The Bklass class now clearly demonstrates method grouping with:

  • 4 class-defined methods: i_, __init__() (2 overloads), classMethod1(), classMethod2()
  • 4 inherited methods: anotherMethod(), callA2(), getValue(), methodA() (all from A_second)

2. Broken Link

I checked all links in the generated HTML. All :py:class:A_second`` references correctly resolve to the A_second class definition. The links are working properly now that all methods are generated.

3. Inherited Methods Order

Currently, inherited methods appear after class-defined methods (following Python documentation conventions). I'm happy to change this to put inherited methods first if you prefer. What would you like?

Screenshot 2026-01-02 at 11 07 01 AM
Screen.Recording.2026-01-02.at.11.07.10.AM.mov

Signed-off-by: Khushi Agrawal <149886195+khushiiagrawal@users.noreply.github.com>
Copy link
Contributor

@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: 1

♻️ Duplicate comments (2)
create_sphinx_demo.py (2)

97-110: Remove unused sphinx_config and quickstart_input variables.

The past review comment correctly identifies this issue: these variables are defined but never used since sphinx-quickstart is invoked with non-interactive command-line flags.


29-29: Incorrect project root calculation will break the script.

The past review comment correctly identifies this issue: Path(__file__).parent resolves to src/scripts/, not the project root. This will cause failures when accessing test files at lines 46-48.

🧹 Nitpick comments (3)
create_sphinx_demo.py (3)

7-7: Remove unused import.

The tempfile module is imported but never used in the script.

🔎 Proposed fix
 import os
 import sys
 import shutil
 import subprocess
-import tempfile
 from pathlib import Path

51-54: Consider using unpacking for cleaner list construction.

The list concatenation can be simplified using the unpacking operator.

🔎 Proposed refactor
-    run_command([
-        "autowrap",
-        "--out", "moduleB.pyx"
-    ] + pxd_files)
+    run_command([
+        "autowrap",
+        "--out", "moduleB.pyx",
+        *pxd_files
+    ])

228-233: Remove unnecessary f-string prefixes.

Lines 228 and 230 use f-string prefixes but contain no placeholders.

🔎 Proposed refactor
-    print(f"\nHTML documentation is available at:")
+    print("\nHTML documentation is available at:")
     print(f"  {html_path}")
-    print(f"\nTo view it, run:")
+    print("\nTo view it, run:")
     print(f"  open {html_path}  # macOS")
     print(f"  xdg-open {html_path}  # Linux")
     print(f"  start {html_path}  # Windows")
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4bacbe9 and 844429d.

📒 Files selected for processing (1)
  • create_sphinx_demo.py
🧰 Additional context used
🪛 Ruff (0.14.10)
create_sphinx_demo.py

13-13: subprocess call: check for execution of untrusted input

(S603)


51-54: Consider ["autowrap", "--out", "moduleB.pyx", *pxd_files] instead of concatenation

Replace with ["autowrap", "--out", "moduleB.pyx", *pxd_files]

(RUF005)


106-106: Local variable quickstart_input is assigned to but never used

Remove assignment to unused variable quickstart_input

(F841)


228-228: f-string without any placeholders

Remove extraneous f prefix

(F541)


230-230: f-string without any placeholders

Remove extraneous f prefix

(F541)


240-240: Do not catch blind exception: Exception

(BLE001)

@khushiiagrawal
Copy link
Contributor Author

@jpfeuffer PTAL.
Thanks!

@jpfeuffer
Copy link
Contributor

Looks good.

The methods are not really sorted by inherited or not though. Might be a sphinx configuration issue.
They have a config option on how to order methods. But "as-source" might not work for cython modules. We should check this.

Signed-off-by: Khushi Agrawal <149886195+khushiiagrawal@users.noreply.github.com>
Copy link
Contributor

@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: 0

♻️ Duplicate comments (3)
create_sphinx_demo.py (3)

29-29: Incorrect project root calculation will break the script.

The script is located at src/scripts/create_sphinx_demo.py, so Path(__file__).parent only resolves to src/scripts/, not the project root. This will cause failures when accessing test files at lines 46-47.


97-110: Remove unused sphinx_config and quickstart_input variables.

The sphinx_config list and quickstart_input string are defined but never used, since the sphinx-quickstart command is invoked with non-interactive command-line flags (-q, --sep, etc.).


118-144: NameError when sys.path.insert already present in conf.py.

If conf_py_path exists but already contains "sys.path.insert" (line 124 check fails), the variable new_lines is never defined. However, line 144 unconditionally references new_lines, causing a NameError. This will crash the script when run multiple times or if conf.py already has the path configuration.

🧹 Nitpick comments (2)
create_sphinx_demo.py (2)

51-54: Consider using unpacking operator for cleaner list construction.

The list concatenation works correctly but using the unpacking operator *pxd_files would be more idiomatic Python.

🔎 Proposed refactor
-    run_command([
-        "autowrap",
-        "--out", "moduleB.pyx"
-    ] + pxd_files)
+    run_command([
+        "autowrap",
+        "--out", "moduleB.pyx",
+        *pxd_files
+    ])

234-240: Remove unnecessary f-string prefixes.

Lines 234 and 236 use f-strings without any placeholders. These can be regular strings.

🔎 Proposed refactor
-    print(f"\nHTML documentation is available at:")
+    print("\nHTML documentation is available at:")
     print(f"  {html_path}")
-    print(f"\nTo view it, run:")
+    print("\nTo view it, run:")
     print(f"  open {html_path}  # macOS")
     print(f"  xdg-open {html_path}  # Linux")
     print(f"  start {html_path}  # Windows")
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 844429d and adccb1c.

📒 Files selected for processing (1)
  • create_sphinx_demo.py
🧰 Additional context used
🪛 Ruff (0.14.10)
create_sphinx_demo.py

13-13: subprocess call: check for execution of untrusted input

(S603)


51-54: Consider ["autowrap", "--out", "moduleB.pyx", *pxd_files] instead of concatenation

Replace with ["autowrap", "--out", "moduleB.pyx", *pxd_files]

(RUF005)


106-106: Local variable quickstart_input is assigned to but never used

Remove assignment to unused variable quickstart_input

(F841)


234-234: f-string without any placeholders

Remove extraneous f prefix

(F541)


236-236: f-string without any placeholders

Remove extraneous f prefix

(F541)


246-246: Do not catch blind exception: Exception

(BLE001)

@khushiiagrawal
Copy link
Contributor Author

Looks good.

The methods are not really sorted by inherited or not though. Might be a sphinx configuration issue. They have a config option on how to order methods. But "as-source" might not work for cython modules. We should check this.

I verified the fix using a Sphinx demo.

With autodoc_member_order = 'bysource' in conf.py, the documentation correctly respects the order of methods in the Cython module:

Class-defined methods (callB) appear first.
Inherited methods (callA) appear after them.
I've attached a screenshot showing Bklass where callB is listed before callA and callA is correctly marked as 'Inherited from A_second'.

Screenshot 2026-01-05 at 1 31 41 AM Screenshot 2026-01-05 at 1 31 52 AM

Signed-off-by: Khushi Agrawal <149886195+khushiiagrawal@users.noreply.github.com>
Copy link
Contributor

@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: 0

🧹 Nitpick comments (1)
tests/test_code_generator_libcpp.py (1)

114-115: Consider verifying Sphinx syntax presence in addition to length.

The updated test checks docstring length but doesn't verify that the Sphinx RST syntax is actually present. Testing for content rather than (or in addition to) length would make this test more robust and directly validate the PR's feature.

🔎 Suggested enhancement to verify Sphinx syntax
 # Length changed due to Sphinx RST syntax (:py:class:`AbstractBaseClass` instead of AbstractBaseClass)
 assert len(libcpp.ABS_Impl1.__doc__) == 98
+assert ":py:class:`AbstractBaseClass`" in libcpp.ABS_Impl1.__doc__

This addition would verify that the Sphinx cross-reference syntax is actually generated, making the test less brittle to unrelated formatting changes.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between adccb1c and d3da2f8.

📒 Files selected for processing (1)
  • tests/test_code_generator_libcpp.py
🧰 Additional context used
🧬 Code graph analysis (1)
tests/test_code_generator_libcpp.py (1)
tests/test_files/libcpp_test.hpp (2)
  • ABS_Impl1 (31-31)
  • ABS_Impl1 (32-32)

@khushiiagrawal
Copy link
Contributor Author

@jpfeuffer PTAL

@timosachsenberg
Copy link
Collaborator

@jpfeuffer good to go?

@jpfeuffer
Copy link
Contributor

Yeah I guess we can try to see how it looks with pyopenms.

Copy link
Contributor

@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: 0

🧹 Nitpick comments (2)
autowrap/CodeGenerator.py (2)

571-580: Consider more robust type handling for wrap-inherits annotation.

The code assumes inherit_annot is either a list or a single string (line 574), but doesn't validate this assumption. If the annotation is accidentally set to another type (dict, tuple, int, etc.), the code may fail unexpectedly.

Additionally, the template extraction at line 578 (base_class.split('[')[0].strip()) is simplistic and could fail for nested templates like "Base<Wrapper<T>>". While this may work for the current use case, it's worth noting.

🛡️ Proposed defensive type validation
 inherit_annot = r_class.cpp_decl.annotations.get("wrap-inherits", [])
 if inherit_annot:
+    # Ensure inherit_annot is a list
+    if isinstance(inherit_annot, str):
+        inherit_list = [inherit_annot]
+    elif isinstance(inherit_annot, list):
+        inherit_list = inherit_annot
+    else:
+        raise ValueError(f"wrap-inherits annotation must be a string or list, got {type(inherit_annot)}")
-    # Generate Sphinx RST links for inherited classes
-    inherit_list = inherit_annot if isinstance(inherit_annot, list) else [inherit_annot]
     inherit_links = []
     for base_class in inherit_list:
+        if not base_class or not isinstance(base_class, str):
+            continue  # Skip empty or invalid entries
         # Extract class name (handle template syntax like "Base[A]")
         base_name = base_class.split('[')[0].strip()
         inherit_links.append(":py:class:`%s`" % base_name)
-    docstring += "      -- Inherits from %s\n" % ", ".join(inherit_links)
+    if inherit_links:
+        docstring += "      -- Inherits from %s\n" % ", ".join(inherit_links)

1850-1879: Consider minor refactoring to reduce code duplication.

The method signature generation (lines 1854-1860 and 1873-1879) is duplicated in both branches. While the logic is correct and the duplication is minimal, consolidating the signature generation could improve maintainability.

♻️ Proposed consolidation to reduce duplication
+    # Generate method signature (same for integral and non-integral)
+    meth_code.add(
+        """
+                 |def __setitem__(self, $in_t_cy key, $res_t_base value):
+                 |    \"\"\"$docstring\"\"\"
+                 """,
+        locals(),
+    )
+
     # Check if the key type is integral for bounds checking
     is_integral = self._is_integral_type(ctype_in)
     size_guard = mdcl.cpp_decl.annotations.get("wrap-upper-limit")
 
-    if is_integral:
-        meth_code.add(
-            """
-                     |def __setitem__(self, $in_t_cy key, $res_t_base value):
-                     |    \"\"\"$docstring\"\"\"
-                     """,
-            locals(),
-        )
-        if size_guard:
-            meth_code.add(
-                """
-                     |    cdef int _idx = $call_arg
-                     |    if _idx < 0:
-                     |        raise IndexError("invalid index %d" % _idx)
-                     |    if _idx >= self.inst.get().$size_guard:
-                     |        raise IndexError("invalid index %d" % _idx)
-                     """,
-                locals(),
-            )
-    else:
+    # Apply bounds checking only for integral types with size guard
+    if is_integral and size_guard:
         meth_code.add(
             """
-                     |def __setitem__(self, $in_t_cy key, $res_t_base value):
-                     |    \"\"\"$docstring\"\"\"
+                     |    cdef int _idx = $call_arg
+                     |    if _idx < 0:
+                     |        raise IndexError("invalid index %d" % _idx)
+                     |    if _idx >= self.inst.get().$size_guard:
+                     |        raise IndexError("invalid index %d" % _idx)
                      """,
             locals(),
         )
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3da2f8 and 6f0d562.

📒 Files selected for processing (1)
  • autowrap/CodeGenerator.py
🔇 Additional comments (5)
autowrap/CodeGenerator.py (5)

800-835: LGTM! Method grouping implementation is correct.

The implementation successfully:

  • Separates class-defined methods from inherited methods using inherited_method_bases
  • Generates constructors first (line 807 comment confirms this is intentional)
  • Generates class-defined methods alphabetically (line 819)
  • Generates inherited methods alphabetically with inherited_from metadata (lines 827-834)
  • Safely retrieves inherited_method_bases with a default empty dict (line 801)

The grouping strategy aligns with the PR objectives to group inherited methods separately after class-defined methods.


917-917: LGTM! Parameter threading for inheritance context is complete.

The inherited_from parameter is correctly threaded through all relevant method signatures:

  • _create_overloaded_method_decl (line 917)
  • create_wrapper_for_method (line 1041)
  • _create_fun_decl_and_input_conversion (line 1165)
  • create_wrapper_for_nonoverloaded_method (line 1397)

The parameter propagates through both overloaded and non-overloaded code paths and correctly appears in the generated docstrings.

Also applies to: 1041-1041, 1165-1165, 1397-1397


991-992: LGTM! Inherited-from annotations are correctly added to docstrings.

Both locations consistently:

  • Check if inherited_from is truthy before adding the annotation
  • Use the Sphinx RST :py:class: syntax for cross-references
  • Apply proper indentation (8 spaces) for docstring formatting

This aligns with the PR objective to annotate inherited methods with their origin class.

Also applies to: 1219-1222


1753-1766: LGTM! Bounds checking correctly gated by integral type detection.

The modification improves type safety by:

  • Applying integer-specific bounds checking only to integral index types (line 1753)
  • Avoiding inappropriate bounds checks on non-integral keys (e.g., string keys in maps)

The logic correctly requires both is_integral and size_guard to be truthy before applying bounds checks (line 1756).


1728-1734: No changes needed—the implementation correctly handles unsigned integral types.

The _is_integral_type method properly detects both signed and unsigned variants because CppType stores type information as separate attributes: base_type (e.g., "int", "long") and is_unsigned (a boolean flag). When parsing types like "unsigned int", the parser strips the unsigned prefix, stores is_unsigned=True, and keeps only "int" in base_type. Therefore, both unsigned int and int have the same base_type value and are correctly identified as integral types. No expansion of the integral_base_types set to include explicit "unsigned int" entries is necessary.

Likely an incorrect or invalid review comment.

@timosachsenberg
Copy link
Collaborator

Does the rabbit make sense?

@jpfeuffer
Copy link
Contributor

Yes!

timosachsenberg and others added 3 commits January 8, 2026 12:26
- Remove unused tempfile import (replaced with webbrowser at top level)
- Remove unused sphinx_config and quickstart_input variables
- Fix NameError when sys.path.insert already present in conf.py by
  simplifying the logic to prepend path configuration
- Use list unpacking (*pxd_files) instead of concatenation
- Remove unnecessary f-string prefixes on lines without placeholders
- Use specific exception types (OSError, webbrowser.Error) instead of
  bare Exception

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. Add defensive type validation for wrap-inherits annotation:
   - Validate that inherit_annot is a string or list, raise ValueError otherwise
   - Skip empty or non-string entries in the inherit list
   - Only add "Inherits from" if there are valid links

2. Reduce code duplication in __setitem__ generation:
   - Move method signature generation outside the if/else branches
   - Consolidate condition to `if is_integral and size_guard:`

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The script served its purpose of verifying that Sphinx RST syntax works
correctly for inheritance documentation. The verification was demonstrated
via screenshots in PR OpenMS#231.

Future testing can be done with pyOpenMS directly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Contributor

@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: 0

🧹 Nitpick comments (2)
autowrap/CodeGenerator.py (2)

579-581: Minor: Consider extracting error message to a constant.

Ruff TRY003 suggests avoiding long messages directly in exception calls. While not critical, extracting the message to a module-level constant improves maintainability.

♻️ Optional refactor

At module level:

_WRAP_INHERITS_TYPE_ERROR = "wrap-inherits annotation must be a string or list, got {}"

Then:

-                raise ValueError(
-                    f"wrap-inherits annotation must be a string or list, got {type(inherit_annot).__name__}"
-                )
+                raise ValueError(_WRAP_INHERITS_TYPE_ERROR.format(type(inherit_annot).__name__))

584-586: Consider logging skipped invalid entries.

Invalid or empty base class entries in wrap-inherits are silently skipped. Adding a debug or warning log would help developers identify configuration issues.

💡 Suggested improvement
             for base_class in inherit_list:
                 if not isinstance(base_class, str) or not base_class:
+                    if base_class:
+                        L.warning(f"Skipping invalid wrap-inherits entry: {base_class!r}")
                     continue  # Skip empty or invalid entries
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f0d562 and 388c0bc.

📒 Files selected for processing (2)
  • autowrap/CodeGenerator.py
  • create_sphinx_demo.py
🧰 Additional context used
🧬 Code graph analysis (1)
autowrap/CodeGenerator.py (1)
autowrap/Code.py (1)
  • add (56-75)
🪛 Ruff (0.14.10)
create_sphinx_demo.py

13-13: subprocess call: check for execution of untrusted input

(S603)

autowrap/CodeGenerator.py

579-581: Avoid specifying long messages outside the exception class

(TRY003)

🔇 Additional comments (9)
create_sphinx_demo.py (3)

13-13: Static analysis false positive - no action needed.

The Ruff S603 warning is a false positive. This script uses subprocess.run with controlled, trusted command arguments constructed from hardcoded paths and values. There is no untrusted user input being executed.


97-101: Verify check=False is intentional for sphinx-quickstart.

Setting check=False suppresses exceptions from sphinx-quickstart, which could hide legitimate errors (e.g., missing Sphinx installation, filesystem issues). If the command can fail non-critically (e.g., when a Sphinx project already exists), consider adding explicit error handling or logging to inform the user.

💡 Suggested improvement
-    run_command(
-        ["sphinx-quickstart", "-q", "--sep", "-p", "Autowrap Demo", 
-         "-a", "Test", "-v", "1.0", "--ext-autodoc", "--ext-viewcode", "."],
-        check=False  
-    )
+    result = run_command(
+        ["sphinx-quickstart", "-q", "--sep", "-p", "Autowrap Demo", 
+         "-a", "Test", "-v", "1.0", "--ext-autodoc", "--ext-viewcode", "."],
+        check=False  
+    )
+    if result.returncode != 0:
+        print("Warning: sphinx-quickstart returned non-zero exit code (may be expected if project exists)")

10-246: Well-structured demo script with clear step delineation.

The script provides an excellent automated demonstration of the inheritance documentation improvements. The 7-step process is well-documented, and the error handling for browser opening (lines 227-232) is robust.

autowrap/CodeGenerator.py (6)

571-591: Robust validation with Sphinx RST link generation.

The wrap-inherits annotation validation correctly handles both string and list inputs, and the Sphinx :py:class: link generation properly handles template syntax (line 588). The validation raises appropriate errors for invalid types.


811-846: Excellent method grouping with proper inheritance context.

The separation of class-defined and inherited methods is well-implemented:

  • Constructor handling is preserved (lines 818-823)
  • Class-defined methods are generated first, then inherited methods (lines 829-845)
  • Both groups are sorted alphabetically for consistency
  • The inherited_from parameter is correctly propagated to annotate inherited methods

This implementation aligns perfectly with the PR objectives to group inherited methods separately with clear origin annotations.


928-928: Complete and consistent inherited_from propagation.

The inherited_from parameter is properly threaded through the entire wrapper generation pipeline:

  • Added to all relevant method signatures with None default (backward compatible)
  • Correctly propagated through overloaded and non-overloaded method paths
  • Annotations use Sphinx :py:class: syntax for cross-references in both runtime docstrings and type stubs
  • The implementation ensures inherited methods are documented with their origin class

Also applies to: 1002-1003, 1052-1052, 1151-1151, 1171-1171, 1176-1176, 1230-1233, 1408-1408, 1417-1417


1747-1812: Improved getitem with proper integral type bounds checking.

The refactored create_special_getitem_method correctly gates integer-specific bounds checking (lines 1767-1777) using the new _is_integral_type helper. This prevents incorrect bounds checks on non-integer index types (e.g., string keys for maps).


1815-1898: Excellent setitem refactoring reduces code duplication.

The consolidated signature generation (lines 1864-1871) and conditional bounds checking (lines 1873-1884) eliminate duplication from the previous implementation. The logic correctly:

  • Generates the method signature once for all key types
  • Applies bounds checking only for integral types with size guards
  • Maintains consistency with the getitem implementation

Based on PR comments, this addresses the CodeRabbit review feedback to reduce duplicated code in __setitem__ generation.


1739-1745: The integral type set is already complete — no changes needed.

The _is_integral_type check correctly covers all necessary integral types. When types like unsigned long or unsigned int are parsed, the base components (long, int) are extracted and stored separately from the is_unsigned flag (as separate attributes in CppType). The method checks only cpp_type.base_type, which already includes all necessary base types: int, long, short, char, plus all fixed-width variants (int8_t through uint64_t).

Test files show all operator[] declarations use size_t (already in the set), and there are no uses of wide character types (wchar_t, char16_t, char32_t) in the codebase.

Likely an incorrect or invalid review comment.

@timosachsenberg timosachsenberg merged commit 34ca6ea into OpenMS:master Jan 8, 2026
10 checks passed
@timosachsenberg
Copy link
Collaborator

Thanks!

timosachsenberg added a commit that referenced this pull request Jan 8, 2026
Changes in this release:
- Fix inheritance documentation with Sphinx RST syntax and method grouping (#231)
- Fix circular import issue with cross-module scoped enum type checking (#238)
- Fix cross-module enum class imports for type assertions (#237)
- Add test for enum-based overload resolution (#234)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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.

Adapt docstring generation regarding inheritance

3 participants