(These are Tableau™ job queue schedules, btw, if you’re curious. But that’s beside the point.)
Some of these dicts may have more or fewer attributes than shown above.
The problem: I want to template these dicts with a specific non-alphabetic order of top-level attributes. Particularly, name, type, and state should be listed first, in that order (if they exist), followed by any other existing attributes in no particular order. A bonus would be the ability to skip some attributes —nextRunAt, createdAt, and updatedAt for example. I don’t care about the ordering of “subordinate” attributes like the internals of frequencyDetails in this case.
My Question: Is there an elegant way of filtering or otherwise iterating over dict attributes to meet such ordering requirements? I know there are inelegant ways to do it; I’m using one now, and it’s xxxx-ugly, full of brute force Jinja tricks that are not particularly robust when faced with dicts having missing or unexpected extra attributes. I realize the ordering doesn’t matter to machines, but humans who have to read these things would appreciate having, for example, the name at the top.
I think any pure jinja implementation is going to be problematic, especially from a “wtf is this doing” perspective.
I’d recommend a custom filter. Potentially that takes a priority list. Something like:
def prio_sort(v, keys=None):
if not keys:
prio_map = {}
else:
prio_map = {k: i for i, k in enumerate(keys)}
return dict(sorted(v.items(), key=lambda k_v: prio_map.get(k_v[0], 65535)))
class FilterModule:
def filters(self):
return {
'prio_sort': prio_sort,
}
Then in your playbook, or wherever you use it:
things | map('prio_sort', keys=['name', 'type', 'state']) | to_nice_yaml(sort_keys=False)
The default for to_nice_yaml is sort_keys=True which would destroy the sorting just done, so you have to disable it.
Note that the default callback (by default) will use json.dumps with sort_keys=True, so depending on how you look at it, without the to_nice_yaml(sort_keys=False) it would potentially appear as though the sorting didn’t work.
Thank you so much, @sivel. That’s exactly what I was hoping for, but my python foo is not likely to have come up with such a clean implementation. (I learned BASIC in 1978. Everything I’ve written since then looks like BASIC.)
I tweaked it a bit to fit our naming schemes, and added the bonus feature of omitting undesired keys. The final result is this:
def dict_reorder(v, first_keys=None, skip_keys=[]):
if not first_keys:
prio_map = {}
else:
prio_map = {k: i for i, k in enumerate(first_keys)}
return dict(
sorted(
[item for item in v.items() if item[0] not in skip_keys],
key=lambda k_v: prio_map.get(k_v[0], 65535),
)
)
class FilterModule:
def filters(self):
return {
'dict_reorder': dict_reorder,
}
That drops right into our local.tablinx collection’s existing plugins/filter/tablinx.py file, and my invocation looks like this:
---
# ./group_vars/mw_tablinx_{{ mw_tablinx_env }}/schedules_live.yml
#
# Generated {{ now() }} by playbook mw_tablinx_utoddl_sched_2.yml.
#
# This file contains all schedules that existed in the {{ mw_tablinx_env }}
# environment at the time the playbook was run. It will be overridden
# on subsequent runs, so don't edit this file.
mw_tablinx_schedules_live_{{ mw_tablinx_env }}:
{% for sch in schedules_all | sort(attribute="state,type,name") %}
- {{ sch | local.tablinx.dict_reorder(first_keys=['name', 'type', 'state'],
skip_keys=['nextRunAt', 'createdAt', 'updatedAt'])
| to_nice_yaml(indent=2, sort_keys=False)
| indent(4) }}
{% endfor %}
That’s still not tiny, but each bit of the Jinja does something comprehensible — which is a great improvement over the fiasco I was creating.