From 6d0722198ff819c5381458dcfdd801f840dea3ab Mon Sep 17 00:00:00 2001 From: Alexis Date: Fri, 21 Aug 2020 01:03:56 +0200 Subject: [PATCH 1/6] api: abstract_paths_from_template - handle properly sequencekey with special format value Fetch paths by ignoring abstract keys --- python/tank/api.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/python/tank/api.py b/python/tank/api.py index a2cdc0178..dc7163b4c 100644 --- a/python/tank/api.py +++ b/python/tank/api.py @@ -681,13 +681,15 @@ def abstract_paths_from_template(self, template, fields): if skip_leaf_level: search_template = template.parent - # now carry out a regular search based on the template - found_files = self.paths_from_template(search_template, fields) - st_abstract_key_names = [ k.name for k in search_template.keys.values() if k.is_abstract ] + # now carry out a regular search based on the template + # skip abstract keys to fetch paths in case of special value + # like SequenceKey with "FORMAT:" value + found_files = self.paths_from_template(search_template, fields, skip_keys=st_abstract_key_names) + # now collapse down the search matches for any abstract fields, # and add the leaf level if necessary abstract_paths = set() From 4a1fe0e71320ab428a9074660bce22de1b988196 Mon Sep 17 00:00:00 2001 From: Alexis Date: Fri, 21 Aug 2020 01:12:51 +0200 Subject: [PATCH 2/6] api: abstract_paths_from_template - handle properly sequencekey with special format value - add tests --- tests/core_tests/test_api.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/core_tests/test_api.py b/tests/core_tests/test_api.py index 9b83e9f98..0279869f1 100644 --- a/tests/core_tests/test_api.py +++ b/tests/core_tests/test_api.py @@ -365,6 +365,15 @@ def test_specify_name(self): ) self.assertEqual(set(expected), set(result)) + def test_sequence_format(self): + expected = [ + os.path.join(self.shot_a_path, "%V", "filename.$F4.exr"), + os.path.join(self.shot_b_path, "%V", "filename.$F4.exr"), + ] + result = self.tk.abstract_paths_from_template( + self.template, {"name": "filename", "SEQ": "FORMAT: $F"} + ) + self.assertEqual(set(expected), set(result)) class TestPathsFromTemplateGlob(TankTestBase): """Tests for Tank.paths_from_template method which check the string sent to glob.glob.""" From e7db0fe08352285f6e7dcdb3f8a3826b86f2dc92 Mon Sep 17 00:00:00 2001 From: Alexis Date: Fri, 21 Aug 2020 10:10:38 +0200 Subject: [PATCH 3/6] api: abstract_paths_from_template sequence key - copy skip_keys argument to prevent unwanted in place edit --- python/tank/api.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/python/tank/api.py b/python/tank/api.py index dc7163b4c..386c24e3e 100644 --- a/python/tank/api.py +++ b/python/tank/api.py @@ -528,9 +528,12 @@ def paths_from_template( :returns: Matching file paths :rtype: List of strings. """ - skip_keys = skip_keys or [] if isinstance(skip_keys, six.string_types): skip_keys = [skip_keys] + elif isinstance(skip_keys, list): + skip_keys = list(skip_keys) + else: + skip_keys = [] # construct local fields dictionary that doesn't include any skip keys: local_fields = dict( From 8ad1831b94afa92bd2992cc4ec938051cfe309fa Mon Sep 17 00:00:00 2001 From: Alexis Date: Fri, 21 Aug 2020 11:43:13 +0200 Subject: [PATCH 4/6] api: abstract_paths_from_template sequence key - ignore only format_spec format --- python/tank/api.py | 17 ++++++++++++++--- python/tank/templatekey.py | 16 ++++++---------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/python/tank/api.py b/python/tank/api.py index 386c24e3e..9beadc985 100644 --- a/python/tank/api.py +++ b/python/tank/api.py @@ -21,6 +21,7 @@ from .errors import TankError, TankMultipleMatchingTemplatesError from .path_cache import PathCache from .template import read_templates +from .templatekey import SequenceKey from . import constants from . import pipelineconfig from . import pipelineconfig_utils @@ -688,10 +689,20 @@ def abstract_paths_from_template(self, template, fields): k.name for k in search_template.keys.values() if k.is_abstract ] + # skip abstract SequenceKey declared as format_spec_format: "FORMAT:" + # then we can list properly existing paths + skip_keys = [] + for k in st_abstract_key_names: + if k not in fields: + continue + if not isinstance(k, SequenceKey): + continue + if not k.is_framespec_format(fields[k]): + continue + skip_keys.append(k) + # now carry out a regular search based on the template - # skip abstract keys to fetch paths in case of special value - # like SequenceKey with "FORMAT:" value - found_files = self.paths_from_template(search_template, fields, skip_keys=st_abstract_key_names) + found_files = self.paths_from_template(search_template, fields, skip_keys=skip_keys) # now collapse down the search matches for any abstract fields, # and add the leaf level if necessary diff --git a/python/tank/templatekey.py b/python/tank/templatekey.py index 4b48d6e9d..7d124b9d9 100644 --- a/python/tank/templatekey.py +++ b/python/tank/templatekey.py @@ -1112,9 +1112,7 @@ def validate(self, value): error_msg += "Valid frame specs: %s\n" % str(self._frame_specs) error_msg += "Valid format strings: %s\n" % full_format_strings - if isinstance(value, six.string_types) and value.startswith( - self.FRAMESPEC_FORMAT_INDICATOR - ): + if self.is_framespec_format(value): # FORMAT: YXZ string - check that XYZ is in VALID_FORMAT_STRINGS pattern = self._extract_format_string(value) if pattern in self.VALID_FORMAT_STRINGS: @@ -1142,11 +1140,11 @@ def validate(self, value): else: return super(SequenceKey, self).validate(value) - def _as_string(self, value): + def is_framespec_format(self, value): + return isinstance(value, six.string_types) and value.startswith(self.FRAMESPEC_FORMAT_INDICATOR) - if isinstance(value, six.string_types) and value.startswith( - self.FRAMESPEC_FORMAT_INDICATOR - ): + def _as_string(self, value): + if self.is_framespec_format(value): # this is a FORMAT: XYZ - convert it to the proper resolved frame spec pattern = self._extract_format_string(value) return self._resolve_frame_spec(pattern, self.format_spec) @@ -1180,9 +1178,7 @@ def _extract_format_string(self, value): """ Returns XYZ given the string "FORMAT: XYZ" """ - if isinstance(value, six.string_types) and value.startswith( - self.FRAMESPEC_FORMAT_INDICATOR - ): + if self.is_framespec_format(value): pattern = value.replace(self.FRAMESPEC_FORMAT_INDICATOR, "").strip() else: # passthrough From 272b21c322faa25716a526fff198f10d6c8f376d Mon Sep 17 00:00:00 2001 From: Alexis Date: Fri, 21 Aug 2020 11:56:31 +0200 Subject: [PATCH 5/6] api: abstract_paths_from_template sequence key - black formatting --- python/tank/api.py | 4 +++- python/tank/templatekey.py | 4 +++- tests/core_tests/test_api.py | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/python/tank/api.py b/python/tank/api.py index 9beadc985..8c4de22fc 100644 --- a/python/tank/api.py +++ b/python/tank/api.py @@ -702,7 +702,9 @@ def abstract_paths_from_template(self, template, fields): skip_keys.append(k) # now carry out a regular search based on the template - found_files = self.paths_from_template(search_template, fields, skip_keys=skip_keys) + found_files = self.paths_from_template( + search_template, fields, skip_keys=skip_keys + ) # now collapse down the search matches for any abstract fields, # and add the leaf level if necessary diff --git a/python/tank/templatekey.py b/python/tank/templatekey.py index 7d124b9d9..1349788e2 100644 --- a/python/tank/templatekey.py +++ b/python/tank/templatekey.py @@ -1141,7 +1141,9 @@ def validate(self, value): return super(SequenceKey, self).validate(value) def is_framespec_format(self, value): - return isinstance(value, six.string_types) and value.startswith(self.FRAMESPEC_FORMAT_INDICATOR) + return isinstance(value, six.string_types) and value.startswith( + self.FRAMESPEC_FORMAT_INDICATOR + ) def _as_string(self, value): if self.is_framespec_format(value): diff --git a/tests/core_tests/test_api.py b/tests/core_tests/test_api.py index 0279869f1..0f0abf069 100644 --- a/tests/core_tests/test_api.py +++ b/tests/core_tests/test_api.py @@ -375,6 +375,7 @@ def test_sequence_format(self): ) self.assertEqual(set(expected), set(result)) + class TestPathsFromTemplateGlob(TankTestBase): """Tests for Tank.paths_from_template method which check the string sent to glob.glob.""" From 5a6ce8b1ab8377abea0623b1dcca3a77840d80e6 Mon Sep 17 00:00:00 2001 From: Alexis Oblet Date: Tue, 1 Sep 2020 12:33:05 +0200 Subject: [PATCH 6/6] api: abstract_paths_from_template use sequence key as python object --- python/tank/api.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/python/tank/api.py b/python/tank/api.py index 8c4de22fc..21efab685 100644 --- a/python/tank/api.py +++ b/python/tank/api.py @@ -685,21 +685,20 @@ def abstract_paths_from_template(self, template, fields): if skip_leaf_level: search_template = template.parent - st_abstract_key_names = [ - k.name for k in search_template.keys.values() if k.is_abstract - ] + st_abstract_keys = [k for k in search_template.keys.values() if k.is_abstract] # skip abstract SequenceKey declared as format_spec_format: "FORMAT:" # then we can list properly existing paths skip_keys = [] - for k in st_abstract_key_names: - if k not in fields: + for k in st_abstract_keys: + key_name = k.name + if key_name not in fields: continue if not isinstance(k, SequenceKey): continue - if not k.is_framespec_format(fields[k]): + if not k.is_framespec_format(fields[key_name]): continue - skip_keys.append(k) + skip_keys.append(key_name) # now carry out a regular search based on the template found_files = self.paths_from_template( @@ -721,8 +720,8 @@ def abstract_paths_from_template(self, template, fields): # by deleting all eye values they will be replaced by %V # as the template is applied. # - for abstract_key_name in st_abstract_key_names: - del cur_fields[abstract_key_name] + for abstract_key in st_abstract_keys: + del cur_fields[abstract_key.name] # pass 2 - if we ignored the leaf level, add those fields back # note that there is no risk that we add abstract fields at this point