# Buggy code
def _get_items(query, server_id=None):
    """query = {
        'url': string,
        'params': dict -- opt, include StartIndex to resume
    }
    """
    items = {"Items": [], "TotalRecordCount": 0, "RestorePoint": {}}

    url = query["url"]
    query.setdefault("params", {})
    params = query["params"]

    try:
        test_params = dict(params)
        test_params["Limit"] = 1
        test_params["EnableTotalRecordCount"] = True

        items["TotalRecordCount"] = _get(url, test_params, server_id=server_id)[
            "TotalRecordCount"
        ]

    except Exception as error:
        LOG.exception(
            "Failed to retrieve the server response %s: %s params:%s",
            url,
            error,
            params,
        )

    else:
        params.setdefault("StartIndex", 0)

        def get_query_params(params, start, count):
            params_copy = dict(params)
            params_copy["StartIndex"] = start
            params_copy["Limit"] = count
            return params_copy

        query_params = [
            get_query_params(params, offset, LIMIT)
            for offset in range(params["StartIndex"], items["TotalRecordCount"], LIMIT)
        ]

        # multiprocessing.dummy.Pool completes all requests in multiple threads but has to
        # complete all tasks before allowing any results to be processed. ThreadPoolExecutor
        # allows for completed tasks to be processed while other tasks are completed on other
        # threads. Dont be a dummy.Pool, be a ThreadPoolExecutor
        import concurrent.futures

        p = concurrent.futures.ThreadPoolExecutor(DTHREADS)

        results = p.map(
            lambda params: _get(url, params, server_id=server_id), query_params
        )

        for params, result in zip(query_params, results):
            query["params"] = params

            result = result or {"Items": []}
            items["Items"].extend(result["Items"])
            # Using items to return data and communicate a restore point back to the callee is
            # a violation of the SRP. TODO: Seperate responsibilities.
            items["RestorePoint"] = query
            yield items
            del items["Items"][:]


# Traceback (ValueError)
2020-02-27 16:39:00.274 T:20092
NOTICE: JELLYFIN.full_sync -> ERROR::jellyfin_kodi\full_sync.py:240 year is out of range
Traceback (most recent call last):
File "jellyfin_kodi\full_sync.py", line 231, in process_library
media[library['CollectionType']](library)
File "jellyfin_kodi\helper\wrapper.py", line 40, in wrapper
result = func(self, dialog=dialog, *args, **kwargs)
File "jellyfin_kodi\full_sync.py", line 363, in musicvideos
obj.musicvideo(mvideo, library=library)
File "jellyfin_kodi\helper\wrapper.py", line 102, in wrapper
return func(*args, **kwargs)
File "jellyfin_kodi\helper\wrapper.py", line 116, in wrapper
return func(self, item, e_item=e_item, *args, **kwargs)
File "jellyfin_kodi\helper\wrapper.py", line 175, in wrapper
return func(self, item, *args, **kwargs)
File "jellyfin_kodi\objects\musicvideos.py", line 79, in musicvideo
obj['Premiere'] = Local(obj['Premiere']) if obj['Premiere'] else datetime.date(obj['Year'] or 2021, 1, 1)
ValueError: year is out of range

# Fixed code
def _get_items(query, server_id=None):
    """query = {
        'url': string,
        'params': dict -- opt, include StartIndex to resume
    }
    """
    items = {"Items": [], "TotalRecordCount": 0, "RestorePoint": {}}

    url = query["url"]
    query.setdefault("params", {})
    params = query["params"]

    try:
        test_params = dict(params)
        test_params["Limit"] = 1
        test_params["EnableTotalRecordCount"] = True

        items["TotalRecordCount"] = _get(url, test_params, server_id=server_id)[
            "TotalRecordCount"
        ]

    except Exception as error:
        LOG.exception(
            "Failed to retrieve the server response %s: %s params:%s",
            url,
            error,
            params,
        )

    else:
        params.setdefault("StartIndex", 0)

        def get_query_params(params, start, count):
            params_copy = dict(params)
            params_copy["StartIndex"] = start
            params_copy["Limit"] = count
            return params_copy

        query_params = [
            get_query_params(params, offset, LIMIT)
            for offset in range(params["StartIndex"], items["TotalRecordCount"], LIMIT)
        ]

        # multiprocessing.dummy.Pool completes all requests in multiple threads but has to
        # complete all tasks before allowing any results to be processed. ThreadPoolExecutor
        # allows for completed tasks to be processed while other tasks are completed on other
        # threads. Dont be a dummy.Pool, be a ThreadPoolExecutor
        p = concurrent.futures.ThreadPoolExecutor(DTHREADS)

        results = p.map(
            lambda params: _get(url, params, server_id=server_id), query_params
        )

        for params, result in zip(query_params, results):
            query["params"] = params

            result = result or {"Items": []}

            # Mitigates #216 till the server validates the date provided is valid
            if result["Items"][0].get("ProductionYear"):
                try:
                    date(result["Items"][0]["ProductionYear"], 1, 1)
                except ValueError:
                    LOG.info(
                        "#216 mitigation triggered. Setting ProductionYear to None"
                    )
                    result["Items"][0]["ProductionYear"] = None

            items["Items"].extend(result["Items"])
            # Using items to return data and communicate a restore point back to the callee is
            # a violation of the SRP. TODO: Seperate responsibilities.
            items["RestorePoint"] = query
            yield items
            del items["Items"][:]