from functools import cmp_to_key from operator import itemgetter from typing import Dict, List, Optional def multikeysort(items: List[Dict], fields: Optional[List[str]]) -> List[dict]: """ Custom solution for sorting list of dicts by multiple fields with support for datetime and None objects. Args: items (List[Dict]): List of items that will be sorted. fields (List[str]): Collection of sorting fields. Returns: List[dict]: Sorted list of items. """ def cmp(x, y): """ Compare the two objects x and y and return an integer according to the outcome. The return value is negative if x < y, zero if x == y and strictly positive if x > y. """ if x is None and y is None: return 0 elif x is None: return -1 elif y is None: return 1 return (x > y) - (x < y) def comparer(left, right): comparer_iter = (cmp(fn(left), fn(right)) * reverse for fn, reverse in comparers) return next((result for result in comparer_iter if result), 0) if not fields: return items if items: not_found_fields = [i for i in fields if i.lstrip("-") not in items[0]] if not_found_fields: raise KeyError(f"Incorrect sort by field(s): {', '.join(not_found_fields)}") comparers = [ ((itemgetter(fld.lstrip("-")), -1) if fld.startswith("-") else (itemgetter(fld.strip()), 1)) for fld in fields ] primary_field = fields[0].lstrip("-") if fields else None if primary_field: _none_items = filter(lambda itm: itm[primary_field] is None, items) _items = filter(lambda itm: itm[primary_field] is not None, items) return sorted(_items, key=cmp_to_key(comparer)) + sorted(_none_items, key=cmp_to_key(comparer)) return sorted(items, key=cmp_to_key(comparer))