Skip to content

Conversation

@timosachsenberg
Copy link
Collaborator

@timosachsenberg timosachsenberg commented Jan 12, 2026

Summary

  • Adds new wrap-view annotation that generates companion ${ClassName}View classes
  • View classes provide direct in-place access to nested C++ objects without copies
  • Methods returning T& (mutable reference) return views instead of copies on view classes
  • Views keep parent objects alive via Python reference chain

Features

>>> outer = Outer()
>>> outer.inner_member.value = 42  # Modifies a COPY - original unchanged!
>>> view = outer.view()
>>> view.inner_member.value = 42   # Modifies IN-PLACE - original changed!
>>> inner_view = view.getInner()   # Returns InnerView, not copy
>>> inner_view.value = 100         # In-place modification

Implementation Details

  • View classes use raw C++ pointers + Python parent references (avoids Cython issues with shared_ptr aliasing across different types)
  • Methods marked wrap-ignore that return T& are still available on view classes (T& returns don't work on main classes due to Cython limitations)
  • Forward references handled by pre-computing classes_with_views before class generation

Test plan

  • Unit tests for annotation parsing (PXDParser, DeclResolver)
  • Unit tests for code generation (CodeGenerator)
  • Integration tests that compile actual Cython code and verify runtime behavior
  • All 88 tests pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added wrap-view support: each annotated class now exposes a view() method and an auto-generated companion View class for in-place member access, mutable-reference-returning methods return views, and nested views preserve lifetimes.
  • Documentation

    • Comprehensive wrap-view guide and examples added.
  • Tests

    • New unit and integration tests validating view generation, property/method behavior, nested views, lifetime semantics, and error cases.

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

Adds a new `wrap-view` annotation that generates companion View classes
alongside regular wrapper classes. View classes provide direct access to
nested C++ objects without creating copies.

Key features:
- `obj.view()` returns a view for in-place member modification
- View properties return nested views (not copies) for wrapped classes
- Methods returning `T&` (mutable reference) return views on view class
- Views keep parent objects alive via Python reference chain

Implementation details:
- View classes use raw pointers + Python parent references (avoids
  Cython issues with shared_ptr aliasing across types)
- Methods marked wrap-ignore that return T& are still available on
  view classes (T& returns don't work on main classes due to Cython
  limitations, but work on view classes via pointer access)
- Forward references handled by pre-computing classes_with_views

Includes unit tests for code generation and integration tests that
compile actual Cython code and verify runtime behavior.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 12, 2026

Warning

Rate limit exceeded

@timosachsenberg has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 28 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 8957bc6 and 790ece1.

📒 Files selected for processing (2)
  • autowrap/CodeGenerator.py
  • tests/test_files/wrap_view_test.hpp
📝 Walkthrough

Walkthrough

This PR adds a "wrap-view" annotation that generates companion View classes for annotated C++ wrappers, updates resolution to preserve certain reference-returning methods, extends code generation to emit view classes/properties/methods, and includes documentation and comprehensive tests for the feature.

Changes

Cohort / File(s) Summary
Annotation & Resolution
autowrap/DeclResolver.py
Added wrap_view: bool to ResolvedClass and populate from decl.annotations["wrap-view"]. Modified wrap-ignore handling to retain methods that return mutable references (is_ref and not is_const).
Code Generation - View Infrastructure
autowrap/CodeGenerator.py
Precompute classes_with_views. Added _create_view_class, _create_view_property_for_attribute, _should_generate_view_method, _create_view_method. Integrate view generation into create_wrapper_for_class. Added uintptr_t import to create_default_cimports.
Test Fixtures & Headers
tests/test_files/wrap_view_test.hpp, tests/test_files/wrap_view_test.pxd
New C++ test header (Inner, Outer, Container) exercising mutable/const refs and nesting; corresponding .pxd declarations including wrap-view / wrap-ignore annotations.
Unit Tests - Generator & Parser
tests/test_code_generator.py, tests/test_decl_resolver.py, tests/test_pxd_parser.py
Added tests for wrap-view parsing and resolution, generator output (view class, properties, view methods), and PXD parsing of wrap-view annotation.
Integration Tests
tests/test_wrap_view.py
New integration test module compiling the wrapper and validating view creation, in-place modification semantics, nested views, lifetime behavior, and string handling.
Documentation
docs/README.md
Documented new wrap-view directive with examples, usage, and notes about wrap-ignore interplay and lifetime semantics.

Sequence Diagram(s)

sequenceDiagram
    participant Parser as PXD Parser
    participant Resolver as DeclResolver
    participant Generator as CodeGenerator
    participant Compiler as Cython

    Parser->>Resolver: Parse declarations with `wrap-view`
    Resolver->>Resolver: Set `wrap_view=True` and retain mutable-ref methods
    Resolver->>Generator: Provide resolved classes (with wrap_view flag)
    Generator->>Generator: Pre-compute `classes_with_views`
    Generator->>Generator: Generate main wrapper class code
    alt wrap_view enabled
        Generator->>Generator: _create_view_class()
        Generator->>Generator: _create_view_property_for_attribute() for attributes
        Generator->>Generator: _should_generate_view_method() for methods
        Generator->>Generator: _create_view_method() to wrap mutable-ref returns
    end
    Generator->>Compiler: Emit combined PYX/PXD/typing stubs for compilation
    Compiler->>Compiler: Compile extension module
Loading
sequenceDiagram
    participant User as Python User
    participant Main as Main Wrapper Class
    participant View as View Class
    participant Cpp as C++ Object

    User->>Main: obj = MainClass()
    Main->>Cpp: construct C++ instance
    User->>Main: view = obj.view()
    Main->>View: instantiate View with _ptr and _parent
    User->>View: view.attribute = new_value
    View->>Cpp: modify underlying C++ via pointer
    User->>Main: copy = obj.get_attr()  -- (by-value getter)
    Main->>Cpp: return copy
    View->>Cpp: modifications persist on parent object
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • jpfeuffer
  • poshul

Poem

🐰 I built a tiny viewing door,

so members change and copies snore.
A View hops in, keeps parents tight,
mutations leap, and chains take flight—
a rabbit cheer for in-place lore!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: introducing a wrap-view annotation for enabling in-place member modification through companion View classes.

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


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.

Verifies that deep views remain valid and functional even after:
- Intermediate views are deleted
- The root container object is deleted

This confirms that all nested views hold a reference to the root
object, keeping the entire C++ object tree alive.

Co-Authored-By: Claude Opus 4.5 <[email protected]>
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: 4

🤖 Fix all issues with AI agents
In @autowrap/CodeGenerator.py:
- Around line 1034-1071: Currently the except blocks around calls to
_create_view_property_for_attribute and _create_view_method swallow all
exceptions and merely log warnings, which can hide failures; change these
handlers to fail fast by either removing the broad try/except or re-raising
after logging (e.g., raise with additional context), so that errors during
generation of view properties/methods for r_class.attribute or r_class.methods
are propagated instead of silently ignored; ensure the log includes the
attribute.cpp_decl.name or method.name and the original exception when
re-raising so view_code and view_typestub_code generation halts with a clear
error.
- Around line 1128-1134: The type stub generation incorrectly uses $name and the
original $py_typing_type when attr_has_view is true; update the stubs.add call
so that when attr_has_view is true the stub property name uses $wrap_as (not
$name) and its type is the view type returned at runtime (e.g., ${AttrBase}View)
instead of $py_typing_type; adjust the logic around stubs.add in
CodeGenerator.py to choose between $py_typing_type and the view type based on
attr_has_view and ensure the stub signature matches the __get__ runtime return.
- Around line 285-293: The pre-computation that builds self.classes_with_views
currently iterates over self.resolved, which only contains declarations from the
current module; change it to iterate over self.all_resolved so cross-module
classes with wrap-view are detected; specifically update the loop in
CodeGenerator that checks isinstance(resolved, ResolvedClass) and
resolved.wrap_view to use self.all_resolved instead of self.resolved so classes
defined in other modules are included in classes_with_views.

In @tests/test_files/wrap_view_test.hpp:
- Around line 52-58: The method Outer::getItemAt(int) accepts negative indices
which leads to signed→unsigned conversion and out-of-bounds access at
items_[index]; fix by validating the input at the top of getItemAt: if index is
negative either throw std::out_of_range (or assert) or handle it explicitly, and
only call items_.resize(index + 1) and return items_[index] after the check so
negative values never reach items_[] or items_.resize with a wrong size.
🧹 Nitpick comments (3)
tests/test_decl_resolver.py (1)

844-844: Prefix unused unpacked variable with underscore.

Static analysis correctly identifies that map_I is never used in either test function. Convention is to prefix with underscore to indicate intentional discard.

♻️ Suggested fix
-    (instance,), map_I = DeclResolver.resolve_decls_from_string(
+    (instance,), _map = DeclResolver.resolve_decls_from_string(

Apply to both lines 844 and 860.

Also applies to: 860-860

tests/test_wrap_view.py (1)

43-52: Consider more specific exception handling in fixture cleanup.

The static analysis correctly flags the bare except on line 51. While cleanup failures are generally non-critical, catching everything silently can hide unexpected issues during test development.

♻️ Suggested fix
     finally:
         # Cleanup generated file
         if os.path.exists(target):
             try:
                 os.remove(target)
-            except:
-                pass
+            except OSError:
+                pass  # Cleanup failure is non-critical in tests
autowrap/CodeGenerator.py (1)

925-1018: View class scaffolding: fix misleading doc + avoid embedded-newline pxd_comment + consider nulling _ptr in __dealloc__.

  • Docstrings mention “shared_ptr” but implementation is raw pointer + Python parent ref.
  • pxd_comment contains a newline without | markers → may render oddly.
  • _is_valid never flips to false because _ptr is never nulled.
Proposed fix (minimal)
-        view_docstring = (
+        view_docstring = (
             "View class for in-place modification of %s members.\n\n"
             "        This class holds a reference to a %s instance and provides\n"
             "        direct access to its members without creating copies. Changes\n"
             "        made through the view are reflected in the original object.\n\n"
-            "        The view keeps the parent object alive via shared_ptr."
+            "        The view keeps the parent object alive via a Python parent reference."
         ) % (cname, cname)

-        pxd_comment = (
-            "# see .pxd file for cdef of _ptr and _parent"
-            if self.write_pxd
-            else "cdef %s* _ptr\n    cdef object _parent" % cy_type
-        )
-
         view_code.add(
             """
                         |
                         |cdef class $view_name:
@@
-                        |    $pxd_comment
+                        |    # Fields declared in .pxd
                         |
+                        |    def __dealloc__(self):
+                        |        self._ptr = NULL
+                        |        self._parent = None
+                        |
                         |    def __repr__(self):
@@
                         """,
             locals(),
         )
+        if not self.write_pxd:
+            view_code.add(f"    cdef {cy_type}* _ptr")
+            view_code.add("    cdef object _parent")
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6236c3d and 7f403b2.

📒 Files selected for processing (9)
  • autowrap/CodeGenerator.py
  • autowrap/DeclResolver.py
  • docs/README.md
  • tests/test_code_generator.py
  • tests/test_decl_resolver.py
  • tests/test_files/wrap_view_test.hpp
  • tests/test_files/wrap_view_test.pxd
  • tests/test_pxd_parser.py
  • tests/test_wrap_view.py
🧰 Additional context used
🧬 Code graph analysis (4)
tests/test_decl_resolver.py (2)
tests/test_pxd_parser.py (1)
  • test_wrap_view_annotation (647-661)
autowrap/DeclResolver.py (1)
  • resolve_decls_from_string (303-304)
tests/test_pxd_parser.py (2)
tests/test_decl_resolver.py (1)
  • test_wrap_view_annotation (842-855)
autowrap/PXDParser.py (1)
  • parse_str (609-618)
autowrap/CodeGenerator.py (2)
autowrap/DeclResolver.py (1)
  • ResolvedClass (168-225)
autowrap/Code.py (2)
  • add (56-75)
  • Code (41-90)
tests/test_wrap_view.py (1)
autowrap/__init__.py (1)
  • parse_and_generate_code (106-119)
🪛 Clang (14.0.6)
tests/test_files/wrap_view_test.hpp

[error] 4-4: 'string' file not found

(clang-diagnostic-error)

🪛 markdownlint-cli2 (0.18.1)
docs/README.md

288-288: Heading style
Expected: setext; Actual: atx

(MD003, heading-style)

🪛 Ruff (0.14.10)
tests/test_decl_resolver.py

844-844: Unpacked variable map_I is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)


860-860: Unpacked variable map_I is never used

Prefix it with an underscore or any other dummy variable pattern

(RUF059)

autowrap/CodeGenerator.py

1043-1043: Do not catch blind exception: Exception

(BLE001)


1066-1066: Do not catch blind exception: Exception

(BLE001)


1324-1324: f-string without any placeholders

Remove extraneous f prefix

(F541)

tests/test_wrap_view.py

43-43: Do not catch blind exception: Exception

(BLE001)


51-51: Do not use bare except

(E722)


51-52: try-except-pass detected, consider logging the exception

(S110)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: test (==3.2.0, 3.10)
  • GitHub Check: test (==3.2.0, 3.11)
  • GitHub Check: test (==3.2.0, 3.12)
  • GitHub Check: test (==3.2.0, 3.13)
  • GitHub Check: test (==3.1.0, 3.10)
  • GitHub Check: test (==3.1.0, 3.13)
  • GitHub Check: test (==3.1.0, 3.11)
  • GitHub Check: test (==3.1.0, 3.12)
🔇 Additional comments (17)
tests/test_decl_resolver.py (1)

842-869: Good test coverage for wrap-view resolution.

The tests correctly verify both the positive case (wrap-view annotation sets wrap_view=True) and the default case (wrap_view=False when absent). The test structure follows existing patterns in this file.

tests/test_pxd_parser.py (1)

647-684: Comprehensive tests for wrap-view annotation parsing.

Good coverage testing both standalone wrap-view annotation parsing as boolean True, and its coexistence with other annotations like wrap-doc and wrap-hash. The assertions correctly verify the annotation value types.

docs/README.md (2)

162-166: Clear documentation for wrap-view directive.

The directive description clearly explains the semantics: main wrapper returns copies, view class allows in-place modification, and views keep parents alive. The bullet points effectively summarize the key behaviors.


288-315: Helpful example demonstrating wrap-view usage.

The example effectively shows both the Cython declaration syntax and Python usage patterns, clearly contrasting copy behavior (outer.inner_member.value = 42) with in-place modification through views (view.inner_member.value = 42). This will help users understand when and how to use the feature.

autowrap/DeclResolver.py (2)

180-180: Correctly adds wrap_view attribute to ResolvedClass.

The wrap_view boolean attribute is properly declared with type hint and initialized from the annotation dictionary with a sensible False default. This aligns with the existing pattern for other annotations like wrap_ignore.

Also applies to: 201-201


569-576: Key logic change: retain wrap-ignore methods returning mutable references.

This change enables view classes to expose methods that return T& even when they're marked wrap-ignore on the main class (since Cython can't handle reference returns directly). The condition result_type.is_ref and not result_type.is_const correctly identifies mutable references, while non-reference and const-reference wrap-ignore methods are still filtered out as intended.

tests/test_code_generator.py (4)

1014-1102: Comprehensive test for View class generation.

This test correctly verifies the core wrap-view functionality:

  • View class (TestClassView) is generated
  • Main class has view() method
  • View class has expected structure (_ptr, _parent, _is_valid, __repr__)

Good use of assertions with descriptive failure messages.


1105-1151: Good negative test case.

Correctly verifies that View classes are NOT generated when wrap-view annotation is absent, preventing unintended code generation.


1154-1265: Thorough property and const attribute tests.

Tests properly verify:

  • Properties are generated for each attribute
  • Properties use _ptr (not inst) for view access
  • Both getter and setter are generated for mutable attributes
  • Const attributes raise AttributeError on set attempts

1268-1388: Good coverage for mutable reference methods.

Tests verify that:

  • Methods returning T& generate view-returning methods
  • View methods return appropriate View types (e.g., InnerView)
  • Methods with arguments correctly pass those arguments through
tests/test_files/wrap_view_test.pxd (1)

1-57: Well-designed test fixture for wrap-view integration tests.

This PXD file provides good coverage of wrap-view scenarios:

  • Basic attributes (value, name)
  • Mutable reference getters with wrap-ignore (getInner(), getItemAt())
  • Const reference getters (getConstInner())
  • Value copy getters (getInnerCopy())
  • Multi-level nesting (ContainerOuterInner)

The inline comments explaining why wrap-ignore is needed for reference returns are helpful for maintainability.

tests/test_wrap_view.py (1)

55-254: Excellent integration test coverage for wrap-view feature.

The test classes comprehensively exercise the wrap-view functionality:

  • Basic tests: Verify View classes exist, view() method works, correct types returned
  • In-place modification: Verify views modify originals while copies don't
  • Mutable ref methods: Verify T& returns work correctly through views
  • Lifetime management: Verify views keep parents alive (critical for memory safety)
  • Deep nesting: Three-level nesting test (ContainerOuterInner)
  • String attributes: Verify string handling through views

The tests effectively validate both the code generation and runtime behavior.

tests/test_files/wrap_view_test.hpp (1)

4-6: Clang can’t find <string>: likely CI/toolchain config, but please verify.
The header itself looks fine; this error usually means the static-analysis compile command isn’t using a C++ driver/stdlib include paths (or is compiling as C).

autowrap/CodeGenerator.py (4)

831-846: Filtering wrap-ignore methods out of main-class generation looks right.
This aligns with the intent that ref-returning access is provided via the View class, not the main wrapper.


886-889: Hooking view-class emission behind r_class.wrap_view is a good integration point.
Please double-check behavior for wrap-manual-memory classes and write_pxd=False builds (the added view() uses self.inst.get()).


2735-2738: uintptr_t cimport addition is appropriate for pointer-based __repr__.
Just ensure this remains compatible with the project’s supported Cython/Python toolchains.


1233-1262: Fix missing cleanup handling, arg-name normalization, and overload-method collision in view method generation.

In _create_view_method, three issues prevent correct code generation:

  1. Cleanup never executed: converter.input_conversion(...) returns a cleanup tuple element, but it's captured and discarded. Unlike other method-generation code in the codebase (constructors, regular methods), cleanups are never collected or applied, risking temporary object leaks.

  2. Missing argument-name normalization: The code iterates for arg_name, arg_type in method.arguments: directly. However, method.arguments can contain missing or invalid names. Call augment_arg_names(method) first—as done in _create_overloaded_method_decl and regular method wrapping—to normalize unnamed arguments to in_0, in_1, etc.

  3. Overloaded methods will collide: The loop for method in methods: generates multiple method definitions with identical names when overloads exist, causing all but the last to be silently overwritten. Without overload dispatch (pending), skip overloaded view methods entirely.

  4. Ruff F541: Line 1321 has f" Returns a view..." with no {} placeholders—should be a plain string.

Suggested fixes
-        # Build argument list
+        # Build argument list (normalize missing names)
+        args = augment_arg_names(method)
         arg_names = []
         arg_types = []
         arg_conversions = Code()
         call_args = []
+        cleanups = []

-        for arg_name, arg_type in method.arguments:
+        for arg_type, arg_name in args:
             converter = self.cr.get(arg_type)
             py_type = converter.matching_python_type(arg_type)
             arg_names.append(arg_name)
             arg_types.append(py_type)

             # Generate input conversion
             conv_code, call_as, cleanup = converter.input_conversion(
                 arg_type, arg_name, len(call_args)
             )
             if conv_code:
                 arg_conversions.add(conv_code)
             call_args.append(call_as)
+            cleanups.append(cleanup)
-        docstring += f"        Returns a view for in-place modification instead of a copy.\n"
+        docstring += "        Returns a view for in-place modification instead of a copy.\n"
         code.add(
             """
             |    # Call C++ method and wrap result in view
             |    cdef $view_type v = $view_type.__new__($view_type)
             |    v._ptr = &self._ptr.$cpp_name($call_args_str)
             |    v._parent = self._parent  # Propagate parent reference
             |    return v
             """,
             locals(),
         )
+        for cleanup in reversed(cleanups):
+            if not cleanup:
+                continue
+            if isinstance(cleanup, (str, bytes)):
+                cleanup = "    %s" % cleanup
+            code.add(cleanup)

In _create_view_class, skip overloaded methods for now:

         for method_name, methods in r_class.methods.items():
             # Skip constructors (same name as class)
             if method_name == cname:
                 continue
+            if len(methods) > 1:
+                L.warning(f"Skipping overloaded view method {cname}.{method_name}; overload dispatch not yet implemented")
+                continue
             for method in methods:

Likely an incorrect or invalid review comment.

- Add note about T& methods needing wrap-ignore on main class
- Document chaining behavior with example
- Document lifetime guarantees (views keep root alive)
- Add example showing views outlive their source objects

Co-Authored-By: Claude Opus 4.5 <[email protected]>
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)
tests/test_wrap_view.py (2)

43-44: Consider catching more specific exceptions or logging the failure reason.

While catching broad Exception is often discouraged, in test fixtures where compilation can fail for many reasons (missing compiler, incompatible Cython version, etc.), this pattern is acceptable. However, you might want to log the exception details for debugging failed CI runs.

♻️ Optional: Add logging for better diagnostics
     except Exception as e:
+        import logging
+        logging.warning(f"wrap_view_module compilation failed: {e}")
         pytest.skip(f"Compilation failed: {e}")

46-52: Avoid bare except clause - use specific exception or at minimum Exception.

The bare except: catches all exceptions including KeyboardInterrupt and SystemExit, which can mask serious issues during test cleanup. Additionally, silently passing on cleanup errors can hide important diagnostic information.

♻️ Proposed fix
     finally:
         # Cleanup generated file
         if os.path.exists(target):
             try:
                 os.remove(target)
-            except:
-                pass
+            except OSError:
+                pass  # File may be locked or already removed
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f403b2 and 8957bc6.

📒 Files selected for processing (2)
  • docs/README.md
  • tests/test_wrap_view.py
🧰 Additional context used
🪛 markdownlint-cli2 (0.18.1)
docs/README.md

288-288: Heading style
Expected: setext; Actual: atx

(MD003, heading-style)

🪛 Ruff (0.14.10)
tests/test_wrap_view.py

43-43: Do not catch blind exception: Exception

(BLE001)


51-51: Do not use bare except

(E722)


51-52: try-except-pass detected, consider logging the exception

(S110)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: test (==3.2.0, 3.10)
  • GitHub Check: test (==3.1.0, 3.12)
  • GitHub Check: test (==3.1.0, 3.13)
  • GitHub Check: test (==3.2.0, 3.12)
  • GitHub Check: test (==3.2.0, 3.11)
  • GitHub Check: test (==3.2.0, 3.13)
  • GitHub Check: test (==3.1.0, 3.11)
  • GitHub Check: test (==3.1.0, 3.10)
🔇 Additional comments (5)
docs/README.md (2)

162-166: Clear and informative directive documentation.

The wrap-view directive documentation is well-structured, covering all key aspects: companion class generation, in-place modification semantics, the requirement for wrap-ignore on T& methods, and lifetime guarantees.


288-336: Comprehensive example section with good coverage of edge cases.

The wrap-view example section effectively demonstrates:

  • Basic usage with wrap-ignore for reference-returning methods
  • The critical distinction between copy vs. in-place modification
  • Chaining syntax
  • Lifetime semantics with practical code examples

This will help users understand when and how to use the feature.

tests/test_wrap_view.py (3)

55-82: Well-structured basic tests for view class generation.

The tests properly verify that View classes are generated with expected naming conventions and that main classes expose the view() method.


192-250: Excellent lifetime tests - critical for verifying reference chain safety.

These tests thoroughly verify the core safety guarantee of the wrap-view feature: that views keep parent objects alive. The test_deep_view_survives_intermediate_and_root_deletion test is particularly valuable as it confirms the reference chain behavior described in the PR objectives.


1-288: Comprehensive test coverage for the wrap-view feature.

The test module provides good coverage across all key aspects:

  • Class generation and naming
  • In-place modification semantics
  • Mutable reference method behavior
  • Lifetime/safety guarantees
  • Deep nesting scenarios
  • String attribute handling

This aligns well with the PR objectives and documentation.

- Use all_resolved instead of resolved for cross-module wrap-view detection
- Fail fast on view property/method generation errors instead of swallowing
- Fix type stubs to show View types when attr_has_view is true
- Add bounds check for negative index in test file getItemAt()

Co-Authored-By: Claude Opus 4.5 <[email protected]>
@jpfeuffer
Copy link
Contributor

Nice! This would work already. I am wondering if it would be nicer to add the annotation to the method to say that the return type should be wrapped as a View.
The disadvantage of this approach is that once you declare a class as View-enabled, ALL reference returns will be views. This can be problematic with e.g. View returns from classes that return things from inside their vector (e.g. MSExperiment). If you change the vector (e.g. just push_back) it might change the memory location and the old View will point to invalid memory.
Might not be a problem if you are careful when declaring certain things as references in the PXD.

@timosachsenberg
Copy link
Collaborator Author

ah true. so you would suggest having the annotation on each method, and this will mean that that the returned type needs to wrapped as a view?
How would the syntax look like? no view() anymore? but encoding in the name?
If we restrict ourself to the methods can we still get all features like chaining those methods?

@jpfeuffer
Copy link
Contributor

jpfeuffer commented Jan 14, 2026

I mean it is up for discussion. My initial idea was to add it to the method. Then autowrap has to check which methods are annotated with "view" and generate additional View wrapper classes/proxies for there return types accordingly.
Another solution would be to create our python wrapper objects with a parent member ALWAYS.
Then only fill it with a reference to the owning python object if a reference was returned (i.e. a method was wrapped with view).
We can also default to a View for things that return a reference. Note the caveats I wrote in the last comment and why it might be dangerous even though you keep the parent alive.

EDIT: Yes I think it should work with chaining, too.

@timosachsenberg
Copy link
Collaborator Author

How will python user figure they got a view? will this be annotated in the Class or Method name? and should we decide between no view XOR view or do should we have both if it is annotated (of course only works if we have either a view() function or a generated name)?

@jpfeuffer
Copy link
Contributor

jpfeuffer commented Jan 14, 2026

In a unified class? By doing object.parent != None. Or you expose a member function that does that (e.g. isView()).
View only makes sense if it is a reference that is returned. If we generate both automatically we will get into trouble if functions in C++ are overloaded by return type (ref vs. copy). We probably need to ignore one then.

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.

3 participants