diff --git a/python/tank/api.py b/python/tank/api.py index a2cdc0178..21efab685 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 @@ -528,9 +529,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( @@ -681,12 +685,25 @@ 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_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_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[key_name]): + continue + skip_keys.append(key_name) - 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 + 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 @@ -703,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 diff --git a/python/tank/templatekey.py b/python/tank/templatekey.py index 4b48d6e9d..1349788e2 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,13 @@ def validate(self, value): else: return super(SequenceKey, self).validate(value) - def _as_string(self, value): - - if isinstance(value, six.string_types) and value.startswith( + def is_framespec_format(self, value): + return 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 +1180,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 diff --git a/tests/core_tests/test_api.py b/tests/core_tests/test_api.py index 9b83e9f98..0f0abf069 100644 --- a/tests/core_tests/test_api.py +++ b/tests/core_tests/test_api.py @@ -365,6 +365,16 @@ 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."""