-
Notifications
You must be signed in to change notification settings - Fork 98
Resolve clip tool ordering in subscription requests #1207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR resolves an issue where the clip tool was incorrectly placed at the end of the tool list when using clip_to_source=True in subscription requests, causing API validation errors due to incorrect tool sequencing.
Changes:
- Introduced tool processing weights to define correct ordering constraints for subscription tools
- Added validation to ensure user-provided tools are in correct order before processing
- Implemented smart insertion logic to place the clip tool at the appropriate position based on tool weights
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| planet/constants.py | Defines _SUBSCRIPTION_TOOL_WEIGHT dictionary mapping tool names to processing order weights (1-4) |
| planet/subscription_request.py | Adds _validate_tool_order() and _insert_clip_tool() helper functions; updates build_request() to validate and correctly insert clip tool |
| tests/unit/test_subscription_request.py | Adds 17 comprehensive parametrized tests covering tool order validation and clip insertion scenarios |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
planet/subscription_request.py
Outdated
| The clip tool is inserted based on its position in the _SUBSCRIPTION_TOOL_WEIGHT | ||
| dictionary, ensuring it comes before any tool that appears after it in the | ||
| processing order. | ||
|
|
||
| Args: | ||
| tool_list: List of tool configurations (modified in place). | ||
| """ | ||
| # Create a position mapping from the _SUBSCRIPTION_TOOL_WEIGHT dictionary | ||
| tool_order = {name: idx for idx, name in enumerate(_SUBSCRIPTION_TOOL_WEIGHT.keys())} | ||
| clip_position = tool_order['clip'] | ||
| insert_index = len(tool_list) # default to end | ||
|
|
||
| for i, tool in enumerate(tool_list): | ||
| tool_type = tool.get('type') | ||
| if tool_type in tool_order: | ||
| if tool_order[tool_type] > clip_position: | ||
| insert_index = i | ||
| break |
Copilot
AI
Feb 5, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The insertion logic uses dictionary enumeration indices instead of weight values, which creates an inconsistency with the validation logic. While this works correctly for tools defined in _SUBSCRIPTION_TOOL_WEIGHT, it conflates positional ordering with weight-based ordering.
Consider refactoring to use weight values directly for consistency with _validate_tool_order. For example, the logic could find the first tool with weight greater than 3, or if no such tool exists, find the last tool with weight less than or equal to 3 and insert after it.
Additionally, unknown tools (not in _SUBSCRIPTION_TOOL_WEIGHT) are silently ignored during position calculation, which could result in clip being inserted after them. This behavior should be documented or handled explicitly - consider whether unknown tools should cause an error or be treated as having a specific default weight.
| The clip tool is inserted based on its position in the _SUBSCRIPTION_TOOL_WEIGHT | |
| dictionary, ensuring it comes before any tool that appears after it in the | |
| processing order. | |
| Args: | |
| tool_list: List of tool configurations (modified in place). | |
| """ | |
| # Create a position mapping from the _SUBSCRIPTION_TOOL_WEIGHT dictionary | |
| tool_order = {name: idx for idx, name in enumerate(_SUBSCRIPTION_TOOL_WEIGHT.keys())} | |
| clip_position = tool_order['clip'] | |
| insert_index = len(tool_list) # default to end | |
| for i, tool in enumerate(tool_list): | |
| tool_type = tool.get('type') | |
| if tool_type in tool_order: | |
| if tool_order[tool_type] > clip_position: | |
| insert_index = i | |
| break | |
| The clip tool is inserted based on its weight in the | |
| _SUBSCRIPTION_TOOL_WEIGHT mapping, ensuring it comes before any tool | |
| with a greater processing weight. | |
| Unknown tools (those whose type is not present in | |
| _SUBSCRIPTION_TOOL_WEIGHT) are ignored when computing the insertion | |
| position and therefore do not affect where the clip tool is placed. | |
| Args: | |
| tool_list: List of tool configurations (modified in place). | |
| """ | |
| clip_weight = _SUBSCRIPTION_TOOL_WEIGHT.get('clip') | |
| # If 'clip' does not have a defined weight, append it to the end to | |
| # avoid raising errors due to missing configuration. | |
| if clip_weight is None: | |
| tool_list.insert(len(tool_list), {'type': 'clip', 'parameters': {}}) | |
| return | |
| insert_index = len(tool_list) # default to end | |
| for i, tool in enumerate(tool_list): | |
| tool_type = tool.get('type') | |
| curr_weight = _SUBSCRIPTION_TOOL_WEIGHT.get(tool_type) | |
| # Skip unknown tools; they do not participate in ordering | |
| if curr_weight is None: | |
| continue | |
| if curr_weight > clip_weight: | |
| insert_index = i | |
| break |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is by design. _SUBSCRIPTION_TOOL_WEIGHT is added as the SDK source of truth for toolchain processing order, and the weight/key order of tools in the dictionary is based on processing order as defined in the documentation page on creating toolchains.
Subscriptions API will validate toolchain processing order according to the documented processing order. However, the SDK also includes the clip_to_source option as a value add, which appends a clip tool pre-flight to the processing sequence. This means that the SDK also needs to validate any user defined toolchain processing sequence, so that clip is inserted into a position in the processing sequence that is valid.
Per the documentation on creating toolchains, bandmath has the same weight as clip, with the caveat that bandmath must come after clip. Which means that when assuming a valid position in the toolchain processing sequence via clip_to_source, we can guarantee validity if clip comes first in it's weight class.
Finally, I took the feedback about unknown tools and added validation logic to raise an error if the user specifies any tool that isn't defined in the tools documentation page, and mirrored in the _SUBSCRIPTION_TOOL_WEIGHT.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
| # to the list of requested tools unless an existing clip tool | ||
| # exists. In that case an exception is raised. | ||
| # Validate that input tool_list is in correct order | ||
| _validate_tool_order(tool_list) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if tool sequencing should be validated in the SDK at all, since it's already validated in the API? I worry about this getting out of sync when changes are made to tools in the API. On the other hand, I see how that info is necessary to store in order to insert clip in the correct place, so maybe there's no avoiding it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW I didn't embark on this task with the intention of validating in the SDK, but as you pointed out, clip needs to be inserted in the correct position in the toolchain. That position is relative to other items in the toolchain, and I couldn't figure out a way to find that correct position without comparing the toolchain against the reference data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. We could add a comment on the precedence file in Subscriptions API to make future devs aware that it needs to be updated here as well.
Resolve clip tool ordering in subscription requests
GH issue #1206
Problem
When using
clip_to_source=Trueinbuild_request(), the clip tool was appended to the end of the tool list, resulting in incorrect processing order. See the documentation on creating toolchains for more information about toolchain processing order.Solution
planet/constants.pyas_SUBSCRIPTION_TOOL_WEIGHT_validate_tool_order()to validate input tool lists are in correct order_insert_clip_tool()to insert clip at the correct position based on tool weightsbuild_request()to use these helper methodsExample
Before:
After:
Testing