devnomads-cli 0.5.1__tar.gz → 0.5.2__tar.gz
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.
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/PKG-INFO +1 -1
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/devnomads_cli.egg-info/PKG-INFO +1 -1
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/dncli.py +33 -5
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/pyproject.toml +1 -1
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/tests/test_helpers.py +34 -3
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/LICENSE +0 -0
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/README.md +0 -0
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/devnomads_cli.egg-info/SOURCES.txt +0 -0
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/devnomads_cli.egg-info/dependency_links.txt +0 -0
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/devnomads_cli.egg-info/entry_points.txt +0 -0
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/devnomads_cli.egg-info/requires.txt +0 -0
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/devnomads_cli.egg-info/top_level.txt +0 -0
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/setup.cfg +0 -0
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/tests/test_cert.py +0 -0
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/tests/test_cli.py +0 -0
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/tests/test_config.py +0 -0
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/tests/test_generate.py +0 -0
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/tests/test_generated_cli.py +0 -0
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/tests/test_hook.py +0 -0
- {devnomads_cli-0.5.1 → devnomads_cli-0.5.2}/tests/test_transfer.py +0 -0
|
@@ -211,16 +211,44 @@ def render(
|
|
|
211
211
|
out_console.print(str(data), soft_wrap=True)
|
|
212
212
|
|
|
213
213
|
|
|
214
|
+
def _summarize_item(item: dict[str, Any]) -> str:
|
|
215
|
+
"""Compact one-line ``key=value`` view of an object's scalar fields for a
|
|
216
|
+
table cell. Nested lists/objects collapse to a count so the line stays
|
|
217
|
+
short; None and empty fields are dropped."""
|
|
218
|
+
|
|
219
|
+
parts = []
|
|
220
|
+
for key, val in item.items():
|
|
221
|
+
if val is None or val == "":
|
|
222
|
+
continue
|
|
223
|
+
if isinstance(val, bool):
|
|
224
|
+
parts.append(f"{key}={'yes' if val else 'no'}")
|
|
225
|
+
elif isinstance(val, dict):
|
|
226
|
+
if val:
|
|
227
|
+
parts.append(f"{key}={{{len(val)}}}")
|
|
228
|
+
elif isinstance(val, list):
|
|
229
|
+
if val:
|
|
230
|
+
parts.append(f"{key}=[{len(val)}]")
|
|
231
|
+
else:
|
|
232
|
+
parts.append(f"{key}={val}")
|
|
233
|
+
return " ".join(parts)
|
|
234
|
+
|
|
235
|
+
|
|
214
236
|
def _cell(value: Any) -> str:
|
|
215
237
|
if value is None:
|
|
216
238
|
return ""
|
|
217
239
|
if isinstance(value, bool):
|
|
218
240
|
return "yes" if value else "no"
|
|
219
|
-
if isinstance(value,
|
|
220
|
-
|
|
221
|
-
):
|
|
222
|
-
|
|
223
|
-
|
|
241
|
+
if isinstance(value, dict):
|
|
242
|
+
return _summarize_item(value)
|
|
243
|
+
if isinstance(value, list):
|
|
244
|
+
if not value:
|
|
245
|
+
return ""
|
|
246
|
+
if all(isinstance(item, dict) for item in value):
|
|
247
|
+
# a list of objects (instances, mailboxes, ips, ...) -> one
|
|
248
|
+
# readable line per item instead of an unreadable JSON blob.
|
|
249
|
+
return "\n".join(_summarize_item(item) for item in value)
|
|
250
|
+
if not any(isinstance(item, (dict, list)) for item in value):
|
|
251
|
+
return ", ".join(str(item) for item in value)
|
|
224
252
|
return json.dumps(value)
|
|
225
253
|
return str(value)
|
|
226
254
|
|
|
@@ -167,11 +167,42 @@ def test_cell():
|
|
|
167
167
|
assert _cell(True) == "yes"
|
|
168
168
|
assert _cell(False) == "no"
|
|
169
169
|
assert _cell(42) == "42"
|
|
170
|
-
|
|
171
|
-
|
|
170
|
+
# a bare object renders as a compact key=value summary
|
|
171
|
+
assert _cell({"a": 1}) == "a=1"
|
|
172
|
+
# scalar lists read as comma-separated values
|
|
172
173
|
assert _cell(["instance_7:80", "instance_8:80"]) == "instance_7:80, instance_8:80"
|
|
173
174
|
assert _cell([]) == ""
|
|
174
|
-
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_cell_list_of_objects_is_summarized():
|
|
178
|
+
# one readable line per item, not a raw JSON blob
|
|
179
|
+
ips = [
|
|
180
|
+
{"type": "v4", "address": "185.223.163.238"},
|
|
181
|
+
{"type": "v6", "address": "2a10:8c80:0:138::1"},
|
|
182
|
+
]
|
|
183
|
+
assert _cell(ips) == (
|
|
184
|
+
"type=v4 address=185.223.163.238\ntype=v6 address=2a10:8c80:0:138::1"
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_cell_summary_drops_empty_and_counts_nested():
|
|
189
|
+
item = {
|
|
190
|
+
"id": 7,
|
|
191
|
+
"image": "geleijn-it/website",
|
|
192
|
+
"ended_at": None,
|
|
193
|
+
"has_pending_changes": 0,
|
|
194
|
+
"volumes": [{"id": 10}],
|
|
195
|
+
"meta": {},
|
|
196
|
+
}
|
|
197
|
+
# None dropped, 0 kept, non-empty nested list shown as a count, empty dict dropped
|
|
198
|
+
assert (
|
|
199
|
+
_cell([item])
|
|
200
|
+
== "id=7 image=geleijn-it/website has_pending_changes=0 volumes=[1]"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def test_cell_mixed_list_stays_json():
|
|
205
|
+
assert _cell([{"a": 1}, "scalar"]) == '[{"a": 1}, "scalar"]'
|
|
175
206
|
|
|
176
207
|
|
|
177
208
|
def test_flatten_row_merges_nested_objects():
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|