Source code for tri_declarative.sort_after
from collections import defaultdict
LAST = object()
[docs]def sort_after(items):
unmoved = []
to_be_moved_by_index = []
to_be_moved_by_name = defaultdict(list)
to_be_moved_last = []
for item in items:
after = getattr(item, 'after', None)
if after is None:
unmoved.append(item)
elif after is LAST:
to_be_moved_last.append(item)
elif isinstance(after, int):
to_be_moved_by_index.append(item)
else:
to_be_moved_by_name[item.after].append(item)
to_be_moved_by_index = sorted(to_be_moved_by_index, key=lambda x: x.after) # pragma: no mutate (infinite loop when x.after changed to None, but if changed to a number manually it exposed a missing test)
def place(x):
yield x
for y in to_be_moved_by_name.pop(x.name, []):
for z in place(y):
yield z
def traverse():
count = 0
while unmoved or to_be_moved_by_index:
while to_be_moved_by_index:
next_by_position_index = to_be_moved_by_index[0].after
if count < next_by_position_index: # pragma: no mutate (infinite loop when mutating < to <=)
break # pragma: no mutate (infinite loop when mutated to continue)
objects_with_index_due = place(to_be_moved_by_index.pop(0))
for x in objects_with_index_due:
yield x
count += 1 # pragma: no mutate
if unmoved:
next_unmoved_and_its_children = place(unmoved.pop(0))
for x in next_unmoved_and_its_children:
yield x
count += 1 # pragma: no mutate
for x in to_be_moved_last:
for y in place(x):
yield y
result = list(traverse())
if to_be_moved_by_name:
available_names = "\n ".join(sorted([x.name for x in items]))
raise KeyError(f'Tried to order after {", ".join(sorted(to_be_moved_by_name.keys()))} but {"that key does" if len(to_be_moved_by_name) == 1 else "those keys do"} not exist.\nAvailable names:\n {available_names}')
return result