bluer-objects 6.104.1__py3-none-any.whl → 6.377.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. bluer_objects/.abcli/abcli.sh +6 -0
  2. bluer_objects/.abcli/alias.sh +11 -0
  3. bluer_objects/.abcli/assets/cd.sh +20 -0
  4. bluer_objects/.abcli/assets/mv.sh +34 -0
  5. bluer_objects/.abcli/assets/publish.sh +37 -0
  6. bluer_objects/.abcli/assets.sh +15 -0
  7. bluer_objects/.abcli/create_test_asset.sh +10 -0
  8. bluer_objects/.abcli/download.sh +3 -1
  9. bluer_objects/.abcli/file.sh +15 -4
  10. bluer_objects/.abcli/gif.sh +18 -0
  11. bluer_objects/.abcli/host.sh +23 -7
  12. bluer_objects/.abcli/ls.sh +19 -8
  13. bluer_objects/.abcli/metadata/download.sh +9 -0
  14. bluer_objects/.abcli/metadata/edit.sh +15 -0
  15. bluer_objects/.abcli/metadata/upload.sh +9 -0
  16. bluer_objects/.abcli/mlflow/browse.sh +2 -0
  17. bluer_objects/.abcli/mlflow/deploy.sh +21 -5
  18. bluer_objects/.abcli/mlflow/lock/lock.sh +11 -0
  19. bluer_objects/.abcli/mlflow/lock/unlock.sh +12 -0
  20. bluer_objects/.abcli/mlflow/lock.sh +15 -0
  21. bluer_objects/.abcli/mlflow.sh +0 -2
  22. bluer_objects/.abcli/pdf/convert.sh +92 -0
  23. bluer_objects/.abcli/pdf.sh +15 -0
  24. bluer_objects/.abcli/storage/clear.sh +2 -0
  25. bluer_objects/.abcli/tests/clone.sh +2 -3
  26. bluer_objects/.abcli/tests/create_test_asset.sh +16 -0
  27. bluer_objects/.abcli/tests/file.sh +64 -0
  28. bluer_objects/.abcli/tests/gif.sh +3 -3
  29. bluer_objects/.abcli/tests/help.sh +27 -4
  30. bluer_objects/.abcli/tests/ls.sh +11 -4
  31. bluer_objects/.abcli/tests/metadata.sh +35 -0
  32. bluer_objects/.abcli/tests/mlflow_lock.sh +30 -0
  33. bluer_objects/.abcli/tests/open.sh +11 -0
  34. bluer_objects/.abcli/tests/open_gif_open.sh +14 -0
  35. bluer_objects/.abcli/tests/pdf.sh +31 -0
  36. bluer_objects/.abcli/tests/storage_clear.sh +11 -0
  37. bluer_objects/.abcli/tests/storage_public_upload.sh +25 -0
  38. bluer_objects/.abcli/tests/storage_status.sh +12 -0
  39. bluer_objects/.abcli/tests/{storage.sh → storage_upload_download.sh} +26 -8
  40. bluer_objects/.abcli/tests/web_is_accessible.sh +17 -0
  41. bluer_objects/.abcli/tests/web_where_am_ai.sh +5 -0
  42. bluer_objects/.abcli/upload.sh +26 -2
  43. bluer_objects/.abcli/url.sh +15 -0
  44. bluer_objects/.abcli/web/is_accessible.sh +13 -0
  45. bluer_objects/.abcli/web/where_am_i.sh +5 -0
  46. bluer_objects/README/__init__.py +24 -9
  47. bluer_objects/README/alias.py +56 -0
  48. bluer_objects/README/consts.py +39 -0
  49. bluer_objects/README/functions.py +127 -205
  50. bluer_objects/README/items.py +78 -6
  51. bluer_objects/README/utils.py +275 -0
  52. bluer_objects/__init__.py +1 -1
  53. bluer_objects/assets/__init__.py +0 -0
  54. bluer_objects/assets/__main__.py +57 -0
  55. bluer_objects/assets/functions.py +62 -0
  56. bluer_objects/config.env +9 -1
  57. bluer_objects/env.py +23 -0
  58. bluer_objects/file/__main__.py +52 -7
  59. bluer_objects/file/functions.py +13 -3
  60. bluer_objects/file/load.py +2 -9
  61. bluer_objects/file/save.py +17 -24
  62. bluer_objects/graphics/__main__.py +7 -0
  63. bluer_objects/graphics/gif.py +11 -7
  64. bluer_objects/graphics/screen.py +9 -8
  65. bluer_objects/help/assets.py +96 -0
  66. bluer_objects/help/create_test_asset.py +22 -0
  67. bluer_objects/help/download.py +17 -3
  68. bluer_objects/help/file.py +59 -0
  69. bluer_objects/help/functions.py +11 -1
  70. bluer_objects/help/gif.py +25 -0
  71. bluer_objects/help/host.py +6 -4
  72. bluer_objects/help/ls.py +26 -3
  73. bluer_objects/help/metadata.py +51 -0
  74. bluer_objects/help/mlflow/__init__.py +23 -2
  75. bluer_objects/help/mlflow/lock.py +52 -0
  76. bluer_objects/help/pdf.py +67 -0
  77. bluer_objects/help/upload.py +10 -3
  78. bluer_objects/help/web.py +38 -0
  79. bluer_objects/host/functions.py +4 -1
  80. bluer_objects/logger/confusion_matrix.py +76 -0
  81. bluer_objects/logger/image.py +110 -0
  82. bluer_objects/logger/stitch.py +107 -0
  83. bluer_objects/markdown.py +8 -6
  84. bluer_objects/metadata/__init__.py +1 -0
  85. bluer_objects/metadata/flatten.py +27 -0
  86. bluer_objects/mlflow/lock/__init__.py +1 -0
  87. bluer_objects/mlflow/lock/__main__.py +58 -0
  88. bluer_objects/mlflow/lock/functions.py +121 -0
  89. bluer_objects/mlflow/logging.py +47 -41
  90. bluer_objects/pdf/__init__.py +1 -0
  91. bluer_objects/pdf/__main__.py +78 -0
  92. bluer_objects/pdf/convert/__init__.py +0 -0
  93. bluer_objects/pdf/convert/batch.py +54 -0
  94. bluer_objects/pdf/convert/combination.py +32 -0
  95. bluer_objects/pdf/convert/convert.py +111 -0
  96. bluer_objects/pdf/convert/image.py +53 -0
  97. bluer_objects/pdf/convert/md.py +97 -0
  98. bluer_objects/pdf/convert/missing.py +96 -0
  99. bluer_objects/pdf/convert/pdf.py +37 -0
  100. bluer_objects/sample.env +6 -0
  101. bluer_objects/storage/WebDAV.py +11 -7
  102. bluer_objects/storage/WebDAVrequest.py +360 -0
  103. bluer_objects/storage/WebDAVzip.py +26 -29
  104. bluer_objects/storage/__init__.py +28 -1
  105. bluer_objects/storage/__main__.py +40 -6
  106. bluer_objects/storage/base.py +84 -5
  107. bluer_objects/storage/policies.py +7 -0
  108. bluer_objects/storage/s3.py +367 -0
  109. bluer_objects/testing/__main__.py +6 -0
  110. bluer_objects/tests/test_README_consts.py +71 -0
  111. bluer_objects/tests/test_README_items.py +128 -0
  112. bluer_objects/tests/test_alias.py +33 -0
  113. bluer_objects/tests/test_env.py +25 -2
  114. bluer_objects/tests/test_file_download.py +25 -0
  115. bluer_objects/tests/test_file_load_save.py +1 -2
  116. bluer_objects/tests/test_file_load_save_text.py +46 -0
  117. bluer_objects/tests/test_graphics_gif.py +2 -0
  118. bluer_objects/tests/test_log_image_grid.py +29 -0
  119. bluer_objects/tests/test_logger_confusion_matrix.py +18 -0
  120. bluer_objects/tests/test_logger_matrix.py +2 -2
  121. bluer_objects/tests/test_logger_stitch_images.py +47 -0
  122. bluer_objects/tests/test_metadata.py +12 -6
  123. bluer_objects/tests/test_metadata_flatten.py +109 -0
  124. bluer_objects/tests/test_mlflow.py +2 -2
  125. bluer_objects/tests/test_mlflow_lock.py +26 -0
  126. bluer_objects/tests/test_objects.py +2 -0
  127. bluer_objects/tests/test_shell.py +34 -0
  128. bluer_objects/tests/test_storage.py +8 -21
  129. bluer_objects/tests/test_storage_base.py +39 -0
  130. bluer_objects/tests/test_storage_s3.py +67 -0
  131. bluer_objects/tests/test_storage_webdav_request.py +75 -0
  132. bluer_objects/tests/test_storage_webdav_zip.py +42 -0
  133. bluer_objects/tests/test_web_is_accessible.py +11 -0
  134. bluer_objects/web/__init__.py +1 -0
  135. bluer_objects/web/__main__.py +31 -0
  136. bluer_objects/web/functions.py +9 -0
  137. {bluer_objects-6.104.1.dist-info → bluer_objects-6.377.1.dist-info}/METADATA +6 -3
  138. bluer_objects-6.377.1.dist-info/RECORD +217 -0
  139. {bluer_objects-6.104.1.dist-info → bluer_objects-6.377.1.dist-info}/WHEEL +1 -1
  140. bluer_objects/.abcli/storage/download_file.sh +0 -9
  141. bluer_objects/.abcli/storage/exists.sh +0 -8
  142. bluer_objects/.abcli/storage/list.sh +0 -8
  143. bluer_objects/.abcli/storage/rm.sh +0 -11
  144. bluer_objects-6.104.1.dist-info/RECORD +0 -143
  145. {bluer_objects-6.104.1.dist-info → bluer_objects-6.377.1.dist-info}/licenses/LICENSE +0 -0
  146. {bluer_objects-6.104.1.dist-info → bluer_objects-6.377.1.dist-info}/top_level.txt +0 -0
@@ -4,10 +4,24 @@ import yaml
4
4
 
5
5
  from blueness import module
6
6
 
7
- from bluer_objects import NAME as MY_NAME, ICON as MY_ICON
7
+ from bluer_objects import NAME as MY_NAME
8
8
  from bluer_objects.metadata import get_from_object
9
- from bluer_objects import file
9
+ from bluer_objects import file, env
10
10
  from bluer_objects import markdown
11
+ from bluer_objects.README.utils import (
12
+ apply_legacy,
13
+ process_assets,
14
+ process_details,
15
+ process_envs,
16
+ process_help,
17
+ process_include,
18
+ process_mermaid,
19
+ process_objects,
20
+ process_title,
21
+ process_variable,
22
+ signature,
23
+ variables,
24
+ )
11
25
  from bluer_objects.logger import logger
12
26
 
13
27
  MY_NAME = module.name(__file__, MY_NAME)
@@ -28,6 +42,7 @@ def build(
28
42
  help_function: Union[Callable[[List[str]], str], None] = None,
29
43
  legacy_mode: bool = True,
30
44
  assets_repo: str = "kamangir/assets",
45
+ download: bool = True,
31
46
  ) -> bool:
32
47
  if path:
33
48
  if path.endswith(".md"):
@@ -55,142 +70,47 @@ def build(
55
70
 
56
71
  table_of_items = markdown.generate_table(items, cols=cols) if cols > 0 else items
57
72
 
58
- signature = [
59
- "",
60
- " ".join(
61
- [
62
- f"[![pylint](https://github.com/kamangir/{REPO_NAME}/actions/workflows/pylint.yml/badge.svg)](https://github.com/kamangir/{REPO_NAME}/actions/workflows/pylint.yml)",
63
- f"[![pytest](https://github.com/kamangir/{REPO_NAME}/actions/workflows/pytest.yml/badge.svg)](https://github.com/kamangir/{REPO_NAME}/actions/workflows/pytest.yml)",
64
- f"[![bashtest](https://github.com/kamangir/{REPO_NAME}/actions/workflows/bashtest.yml/badge.svg)](https://github.com/kamangir/{REPO_NAME}/actions/workflows/bashtest.yml)",
65
- f"[![PyPI version](https://img.shields.io/pypi/v/{MODULE_NAME}.svg)](https://pypi.org/project/{MODULE_NAME}/)",
66
- f"[![PyPI - Downloads](https://img.shields.io/pypi/dd/{MODULE_NAME})](https://pypistats.org/packages/{MODULE_NAME})",
67
- ]
68
- ),
69
- "",
70
- "built by {} [`{}`]({}), based on {}[`{}-{}`]({}).".format(
71
- MY_ICON,
72
- "bluer README",
73
- "https://github.com/kamangir/bluer-objects/tree/main/bluer_objects/README",
74
- f"{ICON} " if ICON else "",
75
- NAME,
76
- VERSION,
77
- f"https://github.com/kamangir/{REPO_NAME}",
78
- ),
79
- ]
80
-
81
73
  success, template = file.load_text(template_filename)
82
74
  if not success:
83
75
  return success
84
76
 
85
- def apply_legacy(line: str) -> str:
86
- for before, after in {
87
- "yaml:::": "metadata:::",
88
- "--help--": "help:::",
89
- "--include": "include:::",
90
- "--table--": "items:::",
91
- "--signature--": "signature:::",
92
- }.items():
93
- line = line.replace(before, after)
94
- return line
95
-
96
77
  if legacy_mode:
97
- logger.info("applying legacy conversions...")
98
- template = [apply_legacy(line) for line in template]
78
+ template = apply_legacy(template)
99
79
 
100
80
  content: List[str] = []
101
81
  mermaid_started: bool = False
102
- variables: Dict[str, str] = {}
103
82
  for template_line in template:
104
83
  if template_line.startswith("ignore:::"):
105
- content_section = [template_line.split(":::", 1)[1].strip()]
106
- else:
107
- if template_line.startswith("set:::"):
108
- key, value = template_line.split("set:::", 1)[1].split(" ", 1)
109
- variables[key] = value
110
- logger.info(f":::{key} = {value}")
111
- continue
112
-
113
- for key, value in variables.items():
114
- template_line = template_line.replace(
115
- f"get:::{key}",
116
- value,
117
- )
118
-
119
- if "assets:::" in template_line:
120
- template_line = " ".join(
121
- [
122
- (
123
- (
124
- "![image](https://github.com/{}/blob/main/{}?raw=true)".format(
125
- assets_repo,
126
- token.split(":::")[1].strip(),
127
- )
128
- if any(
129
- token.endswith(extension)
130
- for extension in ["png", "jpg", "jpeg", "gif"]
131
- )
132
- else "[{}](https://github.com/{}/blob/main/{})".format(
133
- file.name_and_extension(
134
- token.split(":::")[1].strip()
135
- ),
136
- assets_repo,
137
- token.split(":::")[1].strip(),
138
- )
139
- )
140
- if token.startswith("assets:::")
141
- else token
142
- )
143
- for token in template_line.split(" ")
144
- ]
145
- )
146
-
147
- if "object:::" in template_line:
148
- template_line = " ".join(
149
- [
150
- (
151
- "[{}]({}/{}.tar.gz)".format(
152
- token.split(":::")[1].strip(),
153
- "TBA",
154
- token.split(":::")[1].strip(),
155
- )
156
- if token.startswith("object:::")
157
- else token
158
- )
159
- for token in template_line.split(" ")
160
- ]
161
- )
162
-
163
- content_section: List[str] = [template_line]
164
-
165
- if template_line.startswith("details:::"):
166
- suffix = template_line.split(":::", 1)[1]
167
- if suffix:
168
- content_section = [
169
- "",
170
- "<details>",
171
- f"<summary>{suffix}</summary>",
172
- "",
173
- ]
174
- else:
175
- content_section = [
176
- "",
177
- "</details>",
178
- "",
179
- ]
180
- elif template_line.startswith("metadata:::"):
181
- object_name_and_key = template_line.split(":::", 1)[1]
182
- if ":::" not in object_name_and_key:
183
- object_name_and_key += ":::"
184
- object_name, key = object_name_and_key.split(":::", 1)
185
-
186
- value = get_from_object(
187
- object_name,
188
- key,
189
- {},
190
- download=True,
191
- )
192
-
193
- content_section = (
84
+ content += [template_line.split(":::", 1)[1].strip()]
85
+ continue
86
+
87
+ template_line = process_envs(template_line)
88
+
89
+ for key, value in variables.items():
90
+ template_line = template_line.replace(
91
+ f"get:::{key}",
92
+ value,
93
+ )
94
+
95
+ if "metadata:::" in template_line:
96
+ object_name_and_key = template_line.split("metadata:::", 1)[1]
97
+ if " " in object_name_and_key:
98
+ object_name_and_key = object_name_and_key.split(" ", 1)[0]
99
+ if ":::" not in object_name_and_key:
100
+ object_name_and_key += ":::"
101
+ object_name, key = object_name_and_key.split(":::", 1)
102
+
103
+ value = get_from_object(
104
+ object_name,
105
+ key,
106
+ {},
107
+ download=download,
108
+ )
109
+
110
+ logger.info(f"metadata[{object_name_and_key}] = {value}")
111
+
112
+ if template_line.startswith("metadata:::"):
113
+ content += (
194
114
  ["```yaml"]
195
115
  + yaml.dump(
196
116
  value,
@@ -198,84 +118,86 @@ def build(
198
118
  ).split("\n")
199
119
  + ["```"]
200
120
  )
201
- elif template_line.startswith("```mermaid"):
202
- mermaid_started = True
203
- logger.info("🧜🏽‍♀️ detected ...")
204
- elif mermaid_started and template_line.startswith("```"):
205
- mermaid_started = False
206
- elif mermaid_started:
207
- if '"' in template_line and ":::folder" not in template_line:
208
- template_line_pieces = template_line.split('"')
209
- if len(template_line_pieces) != 3:
210
- logger.error(
211
- f"🧜🏽‍♀️ mermaid line not in expected format: {template_line}."
212
- )
213
- return False
214
-
215
- template_line_pieces[1] = (
216
- template_line_pieces[1]
217
- .replace("<", "&lt;")
218
- .replace(">", "&gt;")
219
- .replace(" ", "<br>")
220
- .replace("~~", " ")
221
- )
222
-
223
- content_section = ['"'.join(template_line_pieces)]
224
- elif "items:::" in template_line:
225
- content_section = table_of_items
226
- elif "signature:::" in template_line:
227
- content_section = signature
228
- elif "include:::" in template_line:
229
- include_filename_relative = template_line.split(" ")[1].strip()
230
- include_filename = file.absolute(
231
- include_filename_relative,
232
- file.path(template_filename),
233
- )
121
+ continue
234
122
 
235
- success, content_section = file.load_text(include_filename)
236
- if not success:
237
- return success
238
-
239
- content_section = [
240
- line for line in content_section if not line.startswith("used by:")
241
- ]
242
-
243
- include_title = (template_line.split(" ", 2) + ["", "", ""])[2]
244
- if include_title:
245
- content_section = [f"## {include_title}"] + content_section[1:]
246
-
247
- if "include:::noref" not in template_line:
248
- content_section += [
249
- "using [{}]({}).".format(
250
- file.name(include_filename),
251
- include_filename_relative,
252
- )
253
- ]
254
-
255
- logger.info(f"{MY_NAME}.build: including {include_filename} ...")
256
- elif "help:::" in template_line:
257
- if help_function is not None:
258
- help_command = template_line.split("help:::")[1].strip()
259
-
260
- tokens = help_command.strip().split(" ")[1:]
261
-
262
- help_content = help_function(tokens)
263
- if not help_content:
264
- logger.warning(f"help not found: {help_command}: {tokens}")
265
- return False
266
-
267
- logger.info(f"+= help: {help_command}")
268
- print(help_content)
269
- content_section = [
270
- "```bash",
271
- help_content,
272
- "```",
273
- ]
274
- else:
275
- for macro, macro_value in macros.items():
276
- if macro in template_line:
277
- content_section = macro_value
278
- break
123
+ template_line = template_line.replace(
124
+ f"metadata:::{object_name}:::{key}",
125
+ str(value),
126
+ )
127
+
128
+ if template_line.startswith("set:::"):
129
+ process_variable(template_line)
130
+ continue
131
+
132
+ template_line = process_assets(template_line, assets_repo)
133
+
134
+ template_line = process_objects(template_line)
135
+
136
+ if template_line.startswith("details:::"):
137
+ content += process_details(template_line)
138
+ continue
139
+
140
+ if "items:::" in template_line:
141
+ content += table_of_items
142
+ continue
143
+
144
+ if "include:::" in template_line:
145
+ content += process_include(
146
+ template_line,
147
+ file.path(template_filename),
148
+ )
149
+ continue
150
+
151
+ if "signature:::" in template_line:
152
+ content += signature(
153
+ REPO_NAME,
154
+ NAME,
155
+ ICON,
156
+ MODULE_NAME,
157
+ VERSION,
158
+ )
159
+ continue
160
+
161
+ if template_line.startswith("title:::"):
162
+ success, updated_content = process_title(
163
+ template_line,
164
+ filename,
165
+ )
166
+ if not success:
167
+ return success
168
+
169
+ content += updated_content
170
+ continue
171
+
172
+ if "help:::" in template_line:
173
+ if help_function is None:
174
+ logger.error("help_function not found.")
175
+ return False
176
+
177
+ success, updated_content = process_help(
178
+ template_line,
179
+ help_function,
180
+ )
181
+ if not success:
182
+ return success
183
+
184
+ content += updated_content
185
+ continue
186
+
187
+ content_section = [template_line]
188
+ if template_line.startswith("```mermaid"):
189
+ mermaid_started = True
190
+ logger.info("🧜🏽‍♀️ detected ...")
191
+ elif mermaid_started and template_line.startswith("```"):
192
+ mermaid_started = False
193
+ elif mermaid_started:
194
+ if '"' in template_line and ":::folder" not in template_line:
195
+ content_section = process_mermaid(template_line)
196
+ else:
197
+ for macro, macro_value in macros.items():
198
+ if macro in template_line:
199
+ content_section = macro_value
200
+ break
279
201
 
280
202
  content += content_section
281
203
 
@@ -1,17 +1,43 @@
1
1
  from typing import List, Dict
2
2
 
3
3
 
4
+ # {image,jpg : url}
5
+ def ImageItems(items: Dict[str, str]) -> List[str]:
6
+ def add_raw(url: str) -> str:
7
+ return (
8
+ f"{url}?raw=true" if "github.com" in url and "raw=true" not in url else url
9
+ )
10
+
11
+ return [
12
+ (
13
+ ""
14
+ if not image
15
+ else "[![image]({})]({})".format(
16
+ add_raw(image), url if url else add_raw(image)
17
+ )
18
+ )
19
+ for image, url in items.items()
20
+ ]
21
+
22
+
4
23
  # name, url, marquee, description
5
24
  def Items(
6
25
  items: List[Dict[str, str]],
26
+ sort: bool = False,
7
27
  ) -> List[str]:
8
- return [
28
+ output = [
9
29
  (
10
- "[`{}`]({}) [![image]({})]({}) {}".format(
11
- item["name"],
12
- item.get(
13
- "url",
14
- "#",
30
+ "{}[![image]({})]({}) {}".format(
31
+ (
32
+ "[`{}`]({}) ".format(
33
+ item["name"],
34
+ item.get(
35
+ "url",
36
+ "#",
37
+ ),
38
+ )
39
+ if item["name"]
40
+ else ""
15
41
  ),
16
42
  item.get(
17
43
  "marquee",
@@ -28,3 +54,49 @@ def Items(
28
54
  )
29
55
  for item in items
30
56
  ]
57
+
58
+ if sort:
59
+ output = sorted(output)
60
+
61
+ return output
62
+
63
+
64
+ # dict of {name, url, marquee, description}
65
+ def Items_of_dict(
66
+ dict_of_things: Dict[str, Dict],
67
+ ) -> List[str]:
68
+ return Items(
69
+ sorted(
70
+ [
71
+ {
72
+ "order": info.get("order", thing_name),
73
+ "name": thing_name,
74
+ "marquee": info.get("marquee", ""),
75
+ "url": f"./{thing_name}.md",
76
+ }
77
+ for thing_name, info in dict_of_things.items()
78
+ if thing_name != "template"
79
+ ],
80
+ key=lambda x: x["order"],
81
+ )
82
+ )
83
+
84
+
85
+ # dict of {name, url, marquee, description}
86
+ def list_of_dict(
87
+ dict_of_things: Dict[str, Dict],
88
+ ) -> List[str]:
89
+ return [
90
+ item["text"]
91
+ for item in sorted(
92
+ [
93
+ {
94
+ "text": f"- [{thing_name}](./{thing_name}.md)",
95
+ "order": info.get("order", thing_name),
96
+ }
97
+ for thing_name, info in dict_of_things.items()
98
+ if thing_name != "template"
99
+ ],
100
+ key=lambda x: x["order"],
101
+ )
102
+ ]