Skip to content

Conversation

@ClementHector
Copy link

Context

The parent property of PathTemplate doesn't create a correct PathTemplate when optional keys are used. For example, if you create a template shots/{Sequence}[-{seq_num}]/{Shot}/{Step}/work, the parent property will return a templatePath without the optional [-{seq_num}] part.

Problem

When creating a PathTemplate with optional keys, the parent property omits these keys in the generated path. This can result in incorrect or incomplete paths.

Solution

To solve this issue, instead of passing self.definition to the PathTemplate constructor, we use self._repr_def, which contains the optional keys.

Testing

A test has been added to verify that the parent property now correctly includes optional keys in the generated PathTemplate.

@ClementHector ClementHector marked this pull request as ready for review March 25, 2025 15:24
@julien-lang julien-lang changed the title Fix parent property of PathTemplate in case of optional key SG-38756 Fix parent property of PathTemplate in case of optional key Apr 10, 2025
@julien-lang
Copy link
Contributor

Thanks for your contribution @ClementHector! We are going to review and test.

@ClementHector
Copy link
Author

With pleasure @julien-lang , don't hesitate to ask me for corrections.

@ClementHector
Copy link
Author

Hi @julien-lang, Did you have time to do a review?

@julien-lang julien-lang requested a review from Copilot December 2, 2025 17:51
Copy link
Contributor

Copilot AI left a 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 fixes a bug in the PathTemplate.parent property where optional keys (enclosed in square brackets) were being omitted from the parent path. The fix ensures that when a template like shots/{Sequence}[-{seq_num}]/{Shot}/{Step}/work has its parent calculated, the optional key structure is preserved.

Key changes:

  • Introduced a new _dirname() method to properly handle optional keys when extracting the directory portion of a template path
  • Modified the parent property to use _dirname() instead of os.path.dirname(self.definition)
  • Added comprehensive test coverage for the parent property with optional keys and a new dirname property test suite

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
python/tank/template.py Added _dirname() helper method and updated parent property to correctly preserve optional keys in parent templates
tests/core_tests/test_templatepath.py Added test cases for parent property with optional keys and new TestDirname test class with multiple optional key scenarios
azure-pipelines.yml Added mock package installation step to the CI pipeline

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +562 to +570
Returns the directory part of the template.
:returns: String
"""
dirname, basename = os.path.split(self._repr_def)
if "[" in basename and "]" not in basename:
return dirname.split("[")[0]
return dirname

Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

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

The logic for handling optional keys in _dirname() is incomplete. If the basename contains a [ but no ], it splits by [ and takes only the first part, but this doesn't account for cases where the opening bracket is in the dirname portion. For example, with shots/{Sequence}/[{Shot}]-{Step}, after os.path.split, basename would be [{Shot}]-{Step} (containing both [ and ]), and the condition would be false, but the dirname shots/{Sequence} would be incorrect if it also had an unclosed bracket. Consider a more robust approach that properly handles nested or partial optional sections.

Suggested change
Returns the directory part of the template.
:returns: String
"""
dirname, basename = os.path.split(self._repr_def)
if "[" in basename and "]" not in basename:
return dirname.split("[")[0]
return dirname
Returns the directory part of the template, robustly handling optional sections.
:returns: String
"""
return self._split_path_outside_brackets(self._repr_def)
@staticmethod
def _split_path_outside_brackets(path):
"""
Splits the path at the last path separator that is outside any brackets.
Returns the directory part.
"""
bracket_depth = 0
last_sep_outside = -1
for i, c in enumerate(path):
if c == "[":
bracket_depth += 1
elif c == "]":
bracket_depth = max(0, bracket_depth - 1)
elif c == os.sep and bracket_depth == 0:
last_sep_outside = i
if last_sep_outside == -1:
return ""
return path[:last_sep_outside]

Copilot uses AI. Check for mistakes.
Comment on lines +1189 to +1194
definition = "shots/{Sequence}[-{seq_num}]/{Shot}/{Step}/work"
parent_expected_definitions = [
"shots/{Sequence}-{seq_num}/{Shot}/{Step}",
"shots/{Sequence}/{Shot}/{Step}",
]
parent_expected__repr_def = os.path.dirname(definition)
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

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

The test verifies _definitions and _repr_def but doesn't explain why both are necessary or what they represent. Add a comment explaining that _definitions contains all possible expanded forms (with and without optional keys) while _repr_def is the original representation-preserving form with optional syntax intact.

Copilot uses AI. Check for mistakes.

def test_dirname_with_optional(self):
definition = "shots/{Sequence}/{Shot}[/{Step}]"
template = TemplatePath(definition, {}, root_path=self.project_root)
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

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

The TestDirname test cases pass an empty dictionary for keys instead of self.keys. This may not fully test the interaction between the dirname property and key validation. Consider using self.keys consistently to ensure the tests validate behavior with actual key definitions, or add a comment explaining why empty keys are intentional for these specific tests.

Copilot uses AI. Check for mistakes.

def test_dirname_with_optional_word_and_key(self):
definition = "shots/{Sequence}/{Shot}/[abc_{Step}]"
template = TemplatePath(definition, {}, root_path=self.project_root)
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

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

The TestDirname test cases pass an empty dictionary for keys instead of self.keys. This may not fully test the interaction between the dirname property and key validation. Consider using self.keys consistently to ensure the tests validate behavior with actual key definitions, or add a comment explaining why empty keys are intentional for these specific tests.

Copilot uses AI. Check for mistakes.

def test_dirname_with_optional_key_and_key(self):
definition = "shots/{Sequence}/[{Shot}]-{Step}"
template = TemplatePath(definition, {}, root_path=self.project_root)
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

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

The TestDirname test cases pass an empty dictionary for keys instead of self.keys. This may not fully test the interaction between the dirname property and key validation. Consider using self.keys consistently to ensure the tests validate behavior with actual key definitions, or add a comment explaining why empty keys are intentional for these specific tests.

Copilot uses AI. Check for mistakes.
parent_definition = os.path.dirname(self.definition)
if parent_definition:
parent_definition = self._dirname()
if parent_definition and parent_definition != "/":
Copy link

Copilot AI Dec 2, 2025

Choose a reason for hiding this comment

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

The check parent_definition != \"/\" is Unix-specific and may not work correctly on Windows where root paths use drive letters (e.g., C:\\). Consider using os.path.dirname(parent_definition) != parent_definition or a more platform-agnostic approach to detect root paths.

Suggested change
if parent_definition and parent_definition != "/":
if parent_definition and os.path.dirname(parent_definition) != parent_definition:

Copilot uses AI. Check for mistakes.
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