Skip to content

[Feature] Better submodule path handling#23

Merged
Xmaster6y merged 5 commits intomainfrom
path
Nov 13, 2025
Merged

[Feature] Better submodule path handling#23
Xmaster6y merged 5 commits intomainfrom
path

Conversation

@Xmaster6y
Copy link
Copy Markdown
Owner

@Xmaster6y Xmaster6y commented Nov 13, 2025

What does this PR do?

Key insights about the PR.

Linked Issues

Summary by Sourcery

Improve submodule path handling by migrating from colon-based to angle-bracket custom attribute syntax, adding support for slicing and function calls in resolve_submodule_path, and introducing a submodule_path_to_name helper function.

New Features:

  • Add support for function call expressions (e.g., fn(0)) in resolve_submodule_path
  • Introduce submodule_path_to_name utility to convert submodule paths into flattened names
  • Replace colon-based custom attribute syntax with angle-bracket notation (<...>)

Enhancements:

  • Enhance slice indexing handling in resolve_submodule_path for list slicing expressions
  • Refactor resolve_submodule_path logic by renaming key to path, simplifying regex processing, and unifying eval paths

Tests:

  • Add tests for slice indexing, function call support, and updated angle-bracket syntax in resolve_submodule_path
  • Add comprehensive tests for submodule_path_to_name covering attribute, indexing, and edge-case paths

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Nov 13, 2025

Reviewer's Guide

Refactors resolve_submodule_path to use angle‐bracket syntax, normalize numeric attributes, support slicing and function calls, and adds a new submodule_path_to_name helper, updating tests throughout to cover the new behavior.

Class diagram for updated submodule path handling functions

classDiagram
    class hooks {
        +merge_paths(paths: str) str
        +resolve_submodule_path(root: nn_Module, path: str)
        +submodule_path_to_name(path: str) str
    }

    class nn_Module {
    }
    nn_Module : <<external>>

    hooks ..> nn_Module : uses as root argument
Loading

Class diagram for new submodule_path_to_name helper

classDiagram
    class hooks {
        +submodule_path_to_name(path: str): str
    }
Loading

Flow diagram for submodule path resolution logic

flowchart TD
    A["Input path string"] --> B["Normalize numeric attributes with angle brackets"]
    B --> C["Remove redundant dots and strip leading/trailing dots"]
    C --> D{"Does path contain '<...>'?"}
    D -- Yes --> E["Split at '<', recursively resolve left and right"]
    D -- No --> F["Evaluate path using eval (attribute/index/function call)"]
    E --> G["Return resolved submodule"]
    F --> G
Loading

File-Level Changes

Change Details Files
Refactor resolve_submodule_path parsing and evaluation
  • Rename parameter from key to path and handle empty paths uniformly
  • Preprocess path with regex to wrap numeric-leading attributes and collapse extra dots
  • Switch custom attribute delimiters from ':' to '<>' with updated split logic
  • Extend safe eval to support function calls, slicing, and dot/index syntax
  • Update error messages and eval conditions to reference the new path variable
src/tdhook/hooks.py
Introduce submodule_path_to_name utility
  • Add new function to normalize path strings into dot-separated names
  • Early-return for patterns containing negative indices, slices or parentheses
  • Strip quotes and replace angle brackets and brackets with dots, then collapse dots
  • Trim leading/trailing dots
src/tdhook/hooks.py
Update tests for enhanced path handling
  • Rename and rewrite custom attribute tests to use angle-bracket syntax
  • Add tests for slice indexing and function call support in resolve_submodule_path
  • Add TestSubmodulePathToName class covering various access patterns
  • Adjust existing tests to reflect renamed parameter and new behaviors
tests/test_hooks.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes and they look great!

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location> `src/tdhook/hooks.py:122` </location>
<code_context>
     try:
-        if key.startswith("["):
-            return eval(f"root{key}", {"__builtins__": {}}, safe_dict)
+        if path.startswith(("[", ".")):
+            return eval(f"root{path}", {"__builtins__": {}}, safe_dict)
         else:
</code_context>

<issue_to_address>
**🚨 issue (security):** The eval usage for dynamic path resolution could pose security risks if path input is not strictly controlled.

Even with __builtins__ removed, eval remains unsafe for user-controlled input. Use a safer parsing method or stricter input validation if paths can be influenced externally.
</issue_to_address>

### Comment 2
<location> `tests/test_hooks.py:476-477` </location>
<code_context>
         assert resolve_submodule_path(root, "items[-1]") == "third"
         assert resolve_submodule_path(root, "items[-2]") == "second"

+        # Test slice indexing
+        assert resolve_submodule_path(root, "items[1:3]") == ["second", "third"]
+
     def test_dict_indexing(self):
</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding tests for out-of-range and negative slice indices.

Please add tests for out-of-range slices, negative indices, and empty slices to ensure all edge cases are covered.

```suggestion
        # Test slice indexing
        assert resolve_submodule_path(root, "items[1:3]") == ["second", "third"]

        # Test out-of-range slice indices
        assert resolve_submodule_path(root, "items[2:10]") == ["third"]
        assert resolve_submodule_path(root, "items[10:20]") == []

        # Test negative slice indices
        assert resolve_submodule_path(root, "items[-3:-1]") == ["first", "second"]
        assert resolve_submodule_path(root, "items[-2:]") == ["second", "third"]
        assert resolve_submodule_path(root, "items[:-1]") == ["first", "second"]

        # Test empty slices
        assert resolve_submodule_path(root, "items[1:1]") == []
        assert resolve_submodule_path(root, "items[2:2]") == []
        assert resolve_submodule_path(root, "items[3:3]") == []
```
</issue_to_address>

### Comment 3
<location> `tests/test_hooks.py:492-493` </location>
<code_context>

-    def test_custom_attributes_with_colons(self):
-        """Test custom attributes using colon syntax."""
+    def test_custom_attributes_with_angles(self):
+        """Test custom attributes using angles syntax."""

</code_context>

<issue_to_address>
**suggestion (testing):** Add tests for malformed angle bracket syntax and nested angle brackets.

Please include tests for malformed angle bracket syntax, such as missing brackets and nested brackets, to verify that ValueError is raised as expected.

```suggestion
    def test_custom_attributes_with_angles(self):
        """Test custom attributes using angles syntax."""

    def test_malformed_angle_bracket_syntax(self):
        """Test malformed angle bracket syntax raises ValueError."""
        # Missing closing bracket
        with pytest.raises(ValueError):
            resolve_submodule_path(root, "items<custom")
        # Missing opening bracket
        with pytest.raises(ValueError):
            resolve_submodule_path(root, "items custom>")
        # Only one angle bracket
        with pytest.raises(ValueError):
            resolve_submodule_path(root, "items<custom>")
        # Empty angle brackets
        with pytest.raises(ValueError):
            resolve_submodule_path(root, "items<>")

    def test_nested_angle_brackets(self):
        """Test nested angle brackets raises ValueError."""
        # Nested angle brackets
        with pytest.raises(ValueError):
            resolve_submodule_path(root, "items<outer<inner>>")
        with pytest.raises(ValueError):
            resolve_submodule_path(root, "items<<nested>>")
```
</issue_to_address>

### Comment 4
<location> `tests/test_hooks.py:556-564` </location>
<code_context>
         assert resolve_submodule_path(root, "layers[0].items[1]") == "b"
         assert resolve_submodule_path(root, "name") == "root"

+    def test_function_call(self):
+        """Test function call."""
+
+        class DummyRoot:
+            def __init__(self):
+                self.fn = lambda x: x + 1
+
+        root = DummyRoot()
+        assert resolve_submodule_path(root, "fn(0)") == 1
+
     def test_invalid_paths_raise_value_error(self):
</code_context>

<issue_to_address>
**suggestion (testing):** Add tests for invalid function calls and multiple arguments.

Please add tests for cases such as calling non-callable attributes and passing incorrect argument types or counts, as well as for multiple arguments, to verify robust error handling.

```suggestion
    def test_function_call(self):
        """Test function call and error handling for function calls."""

        class DummyRoot:
            def __init__(self):
                self.fn = lambda x: x + 1
                self.fn_multi = lambda x, y: x * y
                self.value = 42

        root = DummyRoot()
        # Valid single argument call
        assert resolve_submodule_path(root, "fn(0)") == 1
        # Valid multiple argument call
        assert resolve_submodule_path(root, "fn_multi(2, 3)") == 6

        # Non-callable attribute
        with pytest.raises(ValueError):
            resolve_submodule_path(root, "value(1)")

        # Incorrect argument type
        with pytest.raises(Exception):
            resolve_submodule_path(root, "fn('not_an_int')")

        # Incorrect argument count
        with pytest.raises(Exception):
            resolve_submodule_path(root, "fn_multi(1)")
```
</issue_to_address>

### Comment 5
<location> `tests/test_hooks.py:566-568` </location>
<code_context>
+        root = DummyRoot()
+        assert resolve_submodule_path(root, "fn(0)") == 1
+
     def test_invalid_paths_raise_value_error(self):
         """Test that invalid paths raise ValueError."""

</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding more invalid path cases for new syntax.

Please include tests for invalid angle bracket and function call syntax to verify ValueError is raised.

```suggestion
    def test_invalid_paths_raise_value_error(self):
        """Test that invalid paths raise ValueError."""

        class DummyRoot:
            def __init__(self):
                self.fn = lambda x: x + 1
                self.items = ["first", "second", "third"]

        root = DummyRoot()

        import pytest

        # Invalid angle bracket syntax
        with pytest.raises(ValueError):
            resolve_submodule_path(root, "items<0>")

        with pytest.raises(ValueError):
            resolve_submodule_path(root, "fn<1>")

        # Invalid function call syntax
        with pytest.raises(ValueError):
            resolve_submodule_path(root, "fn()")  # missing argument

        with pytest.raises(ValueError):
            resolve_submodule_path(root, "fn(,)")  # invalid argument

        with pytest.raises(ValueError):
            resolve_submodule_path(root, "fn(1,2)")  # too many arguments

        # Existing invalid path cases can remain here
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/tdhook/hooks.py
Comment thread tests/test_hooks.py
Comment thread tests/test_hooks.py
Comment thread tests/test_hooks.py
@codecov
Copy link
Copy Markdown

codecov Bot commented Nov 13, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.29%. Comparing base (4290509) to head (a24783f).
⚠️ Report is 8 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #23      +/-   ##
==========================================
+ Coverage   96.28%   96.29%   +0.01%     
==========================================
  Files          30       30              
  Lines        1909     1918       +9     
==========================================
+ Hits         1838     1847       +9     
  Misses         71       71              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@Xmaster6y Xmaster6y merged commit e49adb1 into main Nov 13, 2025
7 checks passed
@Xmaster6y Xmaster6y deleted the path branch November 13, 2025 15:49
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.

[Feature] Add an autorename feature

1 participant