# Path: mapchete/_executor.py
# class Executor:
#     """
#     Executor factory for dask and concurrent.futures executor
# 
#     Will move into the mapchete core package.
#     """
# 
#     def __new__(cls, *args, concurrency=None, **kwargs):
#         if concurrency == "dask":
#             try:
#                 return DaskExecutor(*args, **kwargs)
#             except ImportError as exc:  # pragma: no cover
#                 raise ImportError(
#                     "this feature requires the mapchete[dask] extra"
#                 ) from exc
# 
#         elif concurrency is None or kwargs.get("max_workers") == 1:
#             return SequentialExecutor(*args, **kwargs)
# 
#         elif concurrency in ["processes", "threads"]:
#             return ConcurrentFuturesExecutor(*args, concurrency=concurrency, **kwargs)
# 
#         else:  # pragma: no cover
#             raise ValueError(
#                 f"concurrency must be one of None, 'processes', 'threads' or 'dask', not {concurrency}"
#             )


from collections import defaultdict
from mapchete._executor import Executor
            from aiohttp import BasicAuth
import logging
import os
import fsspec

        for tile in _tiles():
            yield _exists(tile=tile)

    # for tile directory outputs:
    elif process_tiles:
        logger.debug("sort process tiles by row, this could take a while")
        process_tiles_batches = _batch_tiles_by_row(process_tiles)
    elif output_tiles:
        logger.debug("sort output tiles by row, this could take a while")
        output_tiles_batches = _batch_tiles_by_row(output_tiles)

    if process_tiles_batches:
        yield from _process_tiles_batches_exist(process_tiles_batches, config)
    elif output_tiles_batches:
        yield from _output_tiles_batches_exist(output_tiles_batches, config)


def _batch_tiles_by_row(tiles):
    ordered = defaultdict(set)
    for tile in tiles:
        ordered[tile.row].add(tile)
    return ((t for t in ordered[row]) for row in sorted(list(ordered.keys())))


def _crop_path(path, elements=-3):
    return "/".join(path.split("/")[elements:])


def _output_tiles_batches_exist(output_tiles_batches, config):

# Path: mapchete/_executor.py
# class Executor:
#     """
#     Executor factory for dask and concurrent.futures executor
# 
#     Will move into the mapchete core package.
#     """
# 
#     def __new__(cls, *args, concurrency=None, **kwargs):
#         if concurrency == "dask":
#             try:
#                 return DaskExecutor(*args, **kwargs)
#             except ImportError as exc:  # pragma: no cover
#                 raise ImportError(
#                     "this feature requires the mapchete[dask] extra"
#                 ) from exc
# 
#         elif concurrency is None or kwargs.get("max_workers") == 1:
#             return SequentialExecutor(*args, **kwargs)
# 
#         elif concurrency in ["processes", "threads"]:
#             return ConcurrentFuturesExecutor(*args, concurrency=concurrency, **kwargs)
# 
#         else:  # pragma: no cover
#             raise ValueError(
#                 f"concurrency must be one of None, 'processes', 'threads' or 'dask', not {concurrency}"
#             )


# Path: mapchete/io/_path.py
from collections import defaultdict
from mapchete._executor import Executor
            from aiohttp import BasicAuth
import logging
import os
import fsspec


        for tile in _tiles():
            yield _exists(tile=tile)

    # for tile directory outputs:
    elif process_tiles:
        logger.debug("sort process tiles by row, this could take a while")
        process_tiles_batches = _batch_tiles_by_row(process_tiles)
    elif output_tiles:
        logger.debug("sort output tiles by row, this could take a while")
        output_tiles_batches = _batch_tiles_by_row(output_tiles)

    if process_tiles_batches:
        yield from _process_tiles_batches_exist(process_tiles_batches, config)
    elif output_tiles_batches:
        yield from _output_tiles_batches_exist(output_tiles_batches, config)


def _batch_tiles_by_row(tiles):
    ordered = defaultdict(set)
    for tile in tiles:
        ordered[tile.row].add(tile)
    return ((t for t in ordered[row]) for row in sorted(list(ordered.keys())))


def _crop_path(path, elements=-3):
    return "/".join(path.split("/")[elements:])


def _output_tiles_batches_exist(output_tiles_batches, config):

    with Executor(concurrency="threads") as executor: