# Buggy code
def iocache(func):
    @functools.wraps(func)
    def wrapper(self, *args, **kwargs):
        if self.rule.workflow.iocache.active:
            cache = getattr(self.rule.workflow.iocache, func.__name__)
            normalized = self.rstrip("/")
            if normalized in cache:
                return cache[normalized]
            v = func(self, *args, **kwargs)
            cache[normalized] = v
            return v
        else:
            return func(self, *args, **kwargs)

    return wrapper


# Traceback (FileNotFoundError)
Building DAG of jobs...
Traceback (most recent call last):
File "<base_path>/software/miniconda/envs/snakemake/lib/python3.7/site-packages/snakemake/__init__.py", line 709, in snakemake
keepincomplete=keep_incomplete,
File "<base_path>/software/miniconda/envs/snakemake/lib/python3.7/site-packages/snakemake/workflow.py", line 670, in execute
dag.init()
File "<base_path>/software/miniconda/envs/snakemake/lib/python3.7/site-packages/snakemake/dag.py", line 177, in init
job = self.update(self.file2jobs(file), file=file, progress=progress)
File "<base_path>/software/miniconda/envs/snakemake/lib/python3.7/site-packages/snakemake/dag.py", line 715, in update
progress=progress,
File "<base_path>/software/miniconda/envs/snakemake/lib/python3.7/site-packages/snakemake/dag.py", line 792, in update_
file.inventory()
File "<base_path>/software/miniconda/envs/snakemake/lib/python3.7/site-packages/snakemake/io.py", line 210, in inventory
self._local_inventory(cache)
File "<base_path>/software/miniconda/envs/snakemake/lib/python3.7/site-packages/snakemake/io.py", line 224, in _local_inventory
with os.scandir(path) as scan:
FileNotFoundError: [Errno 2] No such file or directory: '<analysis_patch>/tmp_batch113/codons'

# Fixed code
def iocache(raise_error=False):
    def inner_iocache(func):
        @functools.wraps(func)
        def wrapper(self, *args, **kwargs):
            iocache = self.rule.workflow.iocache
            if not iocache.active or self.inventory_path is None:
                return func(self, *args, **kwargs)

            cache = getattr(iocache, func.__name__)
            # first check if file is present in cache
            if self.inventory_path in cache:
                res = cache[self.inventory_path]
            # check if the folder was cached
            elif iocache.in_inventory(self.inventory_root):
                # as the folder was cached, we do know that the file does not exist
                if raise_error:
                    # make sure that the cache behaves the same as non-cached results
                    raise FileNotFoundError(
                        "No such file or directory: {}".format(self.file)
                    )
                else:
                    return False
            elif self._is_function:
                raise ValueError(
                    "This IOFile is specified as a function and "
                    "may not be used directly."
                )
            else:
                res = IOCACHE_DEFERRED

            if res is IOCACHE_DEFERRED:
                # determine values that are not yet cached
                self._add_to_inventory(func.__name__)
                res = cache[self.inventory_path]

            # makes sure that cache behaves same as non-cached results
            if res is IOCACHE_BROKENSYMLINK:
                raise WorkflowError(
                    "File {} seems to be a broken symlink.".format(self.file)
                )
            elif res is IOCACHE_NOTEXIST:
                raise FileNotFoundError("File {} does not exist.".format(self.file))
            elif res is IOCACHE_NOPERMISSION:
                raise PermissionError(
                    "File {} does not have the required permissions.".format(self.file)
                )
            return res

        return wrapper

    return inner_iocache