{"record":{"id":"92a1c3b57870fd1c","repo":"lllyasviel/Fooocus","slug":"suffix-must-be-a-string-or-tuple-of-strings","errorCode":null,"errorMessage":"\"suffix\" must be a string or tuple of strings","messagePattern":"\"suffix\" must be a string or tuple of strings","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"extras/facexlib/utils/misc.py","lineNumber":96,"sourceCode":"    return cached_file\n\n\ndef scandir(dir_path, suffix=None, recursive=False, full_path=False):\n    \"\"\"Scan a directory to find the interested files.\n    Args:\n        dir_path (str): Path of the directory.\n        suffix (str | tuple(str), optional): File suffix that we are\n            interested in. Default: None.\n        recursive (bool, optional): If set to True, recursively scan the\n            directory. Default: False.\n        full_path (bool, optional): If set to True, include the dir_path.\n            Default: False.\n    Returns:\n        A generator for all the interested files with relative paths.\n    \"\"\"\n\n    if (suffix is not None) and not isinstance(suffix, (str, tuple)):\n        raise TypeError('\"suffix\" must be a string or tuple of strings')\n\n    root = dir_path\n\n    def _scandir(dir_path, suffix, recursive):\n        for entry in os.scandir(dir_path):\n            if not entry.name.startswith('.') and entry.is_file():\n                if full_path:\n                    return_path = entry.path\n                else:\n                    return_path = osp.relpath(entry.path, root)\n\n                if suffix is None:\n                    yield return_path\n                elif return_path.endswith(suffix):\n                    yield return_path\n            else:\n                if recursive:\n                    yield from _scandir(entry.path, suffix=suffix, recursive=recursive)","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/lllyasviel/Fooocus/blob/ae05379cc97bc4361ec8b4ec90193dab21be763f/extras/facexlib/utils/misc.py#L78-L114","documentation":"scandir()'s suffix parameter must be a str or a tuple of str (or None for no filtering). Passing any other type — most commonly a list — raises TypeError immediately, because os.path.endswith only accepts str/tuple. This mirrors mmcv's scandin helper semantics.","triggerScenarios":"Calling scandir(dir_path, suffix=['.png', '.jpg']) (list instead of tuple), suffix=('.png',) is fine but suffix=b'.png' or suffix=Path('.png') is not. Also triggered by helpers that forward a user-supplied extension list verbatim.","commonSituations":"Refactoring code that used glob/glob2 and passing list literals; config files that define extensions as YAML lists which arrive as Python lists.","solutions":["Convert to tuple: scandir(dir, suffix=('.png', '.jpg')) or scandir(dir, tuple(ext_list)).","For a single extension, pass a bare string: suffix='.png'.","Pass suffix=None to accept all files and filter afterwards.","If the list comes from YAML/JSON config, coerce with tuple(cfg['extensions']) at the call site."],"exampleFix":"# before\nfiles = list(scandir(img_dir, suffix=['.png', '.jpg']))\n\n# after\nfiles = list(scandir(img_dir, suffix=('.png', '.jpg')))","handlingStrategy":"type-guard","validationCode":"if suffix is not None and not isinstance(suffix, (str, tuple)):\n    suffix = tuple(suffix)  # coerce lists from YAML/JSON configs\nassert suffix is None or isinstance(suffix, (str, tuple))","typeGuard":"def is_valid_suffix(s) -> bool:\n    return s is None or isinstance(s, str) or (isinstance(s, tuple) and all(isinstance(x, str) for x in s))","tryCatchPattern":"try:\n    files = list(scandir(dir_path, suffix=suffix))\nexcept TypeError as e:\n    raise ValueError(f'Invalid suffix {suffix!r}: use str or tuple of str') from e","preventionTips":["Always write extension filters as tuples ('.png', '.jpg'), never lists.","Coerce config-driven lists with tuple(...) at the boundary.","Wrap third-party scandir calls in a thin helper that normalizes suffix first."],"tags":["facexlib","filesystem","typeerror","scandir"],"backgroundTag":null,"analyzedSha":"ae05379cc97bc4361ec8b4ec90193dab21be763f","analyzedAt":"2026-08-15T04:23:59.533Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}