Skip to content

Conversation

@jan-janssen
Copy link
Member

@jan-janssen jan-janssen commented Sep 10, 2025

Example 1:

from executorlib import SingleNodeExecutor, split_future

def get_tuple(i):
    return 1, 2, i

def echo(i):
    return i

with SingleNodeExecutor(hostname_localhost=True, plot_dependency_graph=True) as exe:
    f = exe.submit(get_tuple, 5)
    f1, f2, f3 = split_future(future=f, n=3)
    f12 = exe.submit(echo, f1)
    f22 = exe.submit(echo, f2)
    f32 = exe.submit(echo, f3)
    print(f12.result(), f22.result(), f32.result())

Example 2:

from executorlib import SingleNodeExecutor, get_item_from_future

def get_dict(i):
    return {"a": 1, "b": i}

def echo(i):
    return i

with SingleNodeExecutor(hostname_localhost=True, plot_dependency_graph=True) as exe:
    f = exe.submit(get_dict, 5)
    f1 = exe.submit(echo, get_item_from_future(future=f, key="a"))
    f2 = exe.submit(echo, get_item_from_future(future=f, key="b"))
    print(f1.result(), f2.result())

Summary by CodeRabbit

  • New Features

    • Split a Future’s tuple/list result into individual index-based futures.
    • Create a future that selects a dictionary item by key.
    • Dependency graph/plotting now understands and labels these selector futures.
  • Refactor

    • Plotting utilities were relocated; update your imports if you reference them directly.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 10, 2025

Walkthrough

Adds FutureSelector utilities (split_future, get_item_from_future), exports them via executorlib. Updates interactive plotting to accept FutureSelector and relocates plotting imports. Adjusts tests to new import path and adds tests for the new FutureSelector behavior with executors and manual futures.

Changes

Cohort / File(s) Summary
Public API export updates
executorlib/__init__.py
Exposes get_item_from_future and split_future from executorlib.standalone.select via imports and all.
New select utilities
executorlib/standalone/select.py
Adds FutureSelector wrapper that proxies a Future and selects an item from its result; provides split_future(future, n) and get_item_from_future(future, key).
Interactive plotting updates
executorlib/task_scheduler/interactive/dependency.py, executorlib/task_scheduler/interactive/dependency_plot.py
Moves plotting imports to dependency_plot; extends plotting to handle FutureSelector in edge creation and argument hashing/serialization.
Tests: select + plotting
tests/test_standalone_select.py, tests/test_singlenodeexecutor_plot_dependency.py, tests/test_fluxjobexecutor_plot.py, tests/test_testclusterexecutor.py
Adds tests for FutureSelector, split_future, and get_item_from_future; updates plotting import paths in existing tests.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant E as Executor (SingleNode)
  participant F as Future
  participant S1 as FutureSelector[0]
  participant S2 as FutureSelector[1]
  participant S3 as FutureSelector[2]

  U->>E: submit(fn)-> Future
  E-->>U: F
  U->>F: split_future(F, 3)
  Note over S1,S3: Each selector proxies F and selects item by index
  U-->>U: Await S1.result(), S2.result(), S3.result()
  E-->>F: set_result((v0, v1, v2))
  F-->>S1: result()-> v0
  F-->>S2: result()-> v1
  F-->>S3: result()-> v2
Loading
sequenceDiagram
  autonumber
  participant DP as dependency_plot
  participant Arg as FutureSelector
  participant UF as Underlying Future

  DP->>DP: convert_arg(Arg)
  DP->>Arg: read _future, _selector
  DP-->>DP: serialize {future, selector, fn_name}
  DP->>DP: add_element(start=UF, end=node, label="[selector]")
  Note right of DP: Supports both int index and str key
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.21% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 a key aspect of the changeset — adding plot support for a new future selector — and is directly related to the PR work (adding FutureSelector, updating dependency_plot, and accompanying tests). It does not explicitly name the new public helper exports (split_future and get_item_from_future), but it is concise and relevant to the main change.

Poem

I split a future, nibble three—
Carrots of results for you and me.
A key for crunch, an index bite,
Graph lines hop in tidy light.
Thump-thump, I queue, I plot, I chew—
Small ears, big tasks, all hopping through. 🥕🐇

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch plot_split

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.

@codecov
Copy link

codecov bot commented Sep 10, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.17%. Comparing base (d827ac5) to head (4a41b75).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #820      +/-   ##
==========================================
+ Coverage   98.12%   98.17%   +0.05%     
==========================================
  Files          33       34       +1     
  Lines        1650     1698      +48     
==========================================
+ Hits         1619     1667      +48     
  Misses         31       31              

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Base automatically changed from split to main September 11, 2025 05:02
@jan-janssen jan-janssen marked this pull request as ready for review September 11, 2025 05:02
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

Caution

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

⚠️ Outside diff range comments (1)
executorlib/task_scheduler/interactive/dependency_plot.py (1)

52-61: Handle FutureSelector inside containers (lists/dicts) for correct plotting

Lists/dicts that contain FutureSelector elements are currently not recognized (only Future is checked), so edges for nested selectors are missed.

-        elif isinstance(arg, list) and any(isinstance(a, Future) for a in arg):
+        elif isinstance(arg, list) and any(isinstance(a, (Future, FutureSelector)) for a in arg):
@@
-            for i, a in enumerate(arg):
-                if isinstance(a, Future):
-                    add_element(arg=a, link_to=node_id, label="ind: " + str(i))
+            for i, a in enumerate(arg):
+                if isinstance(a, (Future, FutureSelector)):
+                    add_element(arg=a, link_to=node_id, label="ind: " + str(i))
@@
-        elif isinstance(arg, dict) and any(isinstance(a, Future) for a in arg.values()):
+        elif isinstance(arg, dict) and any(isinstance(a, (Future, FutureSelector)) for a in arg.values()):
@@
-            for kt, vt in arg.items():
-                if isinstance(vt, Future):
-                    add_element(arg=vt, link_to=node_id, label="key: " + kt)
+            for kt, vt in arg.items():
+                if isinstance(vt, (Future, FutureSelector)):
+                    add_element(arg=vt, link_to=node_id, label="key: " + str(kt))

Also applies to: 62-74

🧹 Nitpick comments (6)
executorlib/standalone/select.py (2)

23-25: Pass the FutureSelector itself to callbacks

Forwarding the underlying Future to add_done_callback can surprise users (callback receives the parent Future instead of the selector). Wrap the callback so it gets self.

-    def add_done_callback(self, fn) -> None:
-        return self._future.add_done_callback(fn=fn)
+    def add_done_callback(self, fn) -> None:
+        def _wrap(_):
+            fn(self)
+        return self._future.add_done_callback(_wrap)

11-16: Clarify/codify cancellation semantics

cancel() on a selector cancels the underlying parent future (affecting all selectors). If this is intended, document it in the class docstring; otherwise, consider making selector-level cancellation a no-op.

executorlib/task_scheduler/interactive/dependency_plot.py (1)

36-43: Disambiguate selector edge labels

For dict selectors the edge label is just the key string (e.g., "b"), while list/tuple selectors show no index prefix. Use consistent prefixes to match existing dict/list labeling patterns.

-            edge_lst.append(
-                {
-                    "start": hash_id_dict[future_hash_inverse_dict[arg._future]],
-                    "end": link_to,
-                    "label": label + str(arg._selector),
-                }
-            )
+            edge_lst.append(
+                {
+                    "start": hash_id_dict[future_hash_inverse_dict[arg._future]],
+                    "end": link_to,
+                    "label": label
+                    + ("key: " if isinstance(arg._selector, str) else "ind: ")
+                    + str(arg._selector),
+                }
+            )
tests/test_singlenodeexecutor_plot_dependency.py (1)

307-330: Nice executable examples for selectors in plotting mode

Covers both tuple split and dict item selection with plot_dependency_graph=True. Consider adding one case where a selector is nested inside a list/dict argument to exercise the container handling (after the suggested fix).

tests/test_standalone_select.py (2)

16-18: Silence ARG001: unused parameter

Rename the unused argument to _i to satisfy linters.

-def function_with_exception(i):
+def function_with_exception(_i):
     raise RuntimeError()

60-66: Optional: strengthen callback assertion (if callback wraps selector)

If you adopt the add_done_callback wrapper suggestion, assert that the callback sees the selected value (not the full result).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4568507 and 4a41b75.

📒 Files selected for processing (8)
  • executorlib/__init__.py (2 hunks)
  • executorlib/standalone/select.py (1 hunks)
  • executorlib/task_scheduler/interactive/dependency.py (1 hunks)
  • executorlib/task_scheduler/interactive/dependency_plot.py (3 hunks)
  • tests/test_fluxjobexecutor_plot.py (1 hunks)
  • tests/test_singlenodeexecutor_plot_dependency.py (3 hunks)
  • tests/test_standalone_select.py (1 hunks)
  • tests/test_testclusterexecutor.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
tests/test_fluxjobexecutor_plot.py (1)
executorlib/task_scheduler/interactive/dependency_plot.py (1)
  • generate_nodes_and_edges_for_plotting (10-91)
executorlib/task_scheduler/interactive/dependency_plot.py (1)
executorlib/standalone/select.py (1)
  • FutureSelector (5-43)
tests/test_testclusterexecutor.py (1)
executorlib/task_scheduler/interactive/dependency_plot.py (1)
  • generate_nodes_and_edges_for_plotting (10-91)
executorlib/task_scheduler/interactive/dependency.py (1)
executorlib/task_scheduler/base.py (1)
  • TaskSchedulerBase (17-198)
tests/test_standalone_select.py (3)
executorlib/executor/single.py (1)
  • SingleNodeExecutor (20-190)
executorlib/standalone/select.py (13)
  • split_future (46-57)
  • get_item_from_future (60-72)
  • FutureSelector (5-43)
  • result (26-31)
  • done (20-21)
  • add_done_callback (23-24)
  • set_running_or_notify_cancel (36-37)
  • running (17-18)
  • set_result (39-40)
  • cancel (11-12)
  • cancelled (14-15)
  • set_exception (42-43)
  • exception (33-34)
executorlib/standalone/serialize.py (1)
  • cloudpickle_register (9-28)
executorlib/__init__.py (1)
executorlib/standalone/select.py (2)
  • get_item_from_future (60-72)
  • split_future (46-57)
tests/test_singlenodeexecutor_plot_dependency.py (3)
executorlib/standalone/select.py (2)
  • split_future (46-57)
  • get_item_from_future (60-72)
executorlib/task_scheduler/interactive/dependency_plot.py (1)
  • generate_nodes_and_edges_for_plotting (10-91)
executorlib/task_scheduler/interactive/dependency.py (1)
  • submit (113-153)
🪛 Ruff (0.12.2)
tests/test_standalone_select.py

16-16: Unused function argument: i

(ARG001)

⏰ 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). (2)
  • GitHub Check: uml
  • GitHub Check: notebooks_integration
🔇 Additional comments (5)
executorlib/task_scheduler/interactive/dependency.py (1)

14-19: LGTM: import relocation

Importing plotting utilities from interactive.dependency_plot aligns with the refactor; no behavior change here.

executorlib/__init__.py (2)

28-28: Re-exporting selector utilities via top-level API is good

Surface area looks right and matches tests.


70-71: all updated appropriately

Exports include get_item_from_future and split_future.

tests/test_fluxjobexecutor_plot.py (1)

6-6: Import path update looks correct

Matches the new module location.

tests/test_testclusterexecutor.py (1)

7-7: Import path update looks correct

Consistent with the plotting refactor.

@jan-janssen jan-janssen merged commit 5b71e23 into main Sep 11, 2025
58 of 62 checks passed
@jan-janssen jan-janssen deleted the plot_split branch September 11, 2025 05:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants