Skip to content

Commit ffd9119

Browse files
NRL-1798 Add output file
1 parent 2ec9887 commit ffd9119

File tree

1 file changed

+101
-42
lines changed

1 file changed

+101
-42
lines changed

scripts/delete_pointers_by_id.py

Lines changed: 101 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/usr/bin/env python
22
import json
3+
import os
4+
import tempfile
35
from datetime import datetime, timedelta, timezone
46
from typing import Any, Dict, List
57

@@ -156,11 +158,53 @@ def _batch_delete_pointers(
156158
return pointers_deleted, sorted(failed_deletes_set)
157159

158160

161+
def _write_result_file(result: Dict[str, Any], output_file: str) -> None:
162+
"""
163+
Atomically write result dict to output_file as JSON.
164+
Raises on failure.
165+
"""
166+
out_dir = os.path.dirname(os.path.abspath(output_file)) or "."
167+
with tempfile.NamedTemporaryFile(
168+
"w", delete=False, dir=out_dir, prefix=".tmp_delete_results_", suffix=".json"
169+
) as tf:
170+
json.dump(result, tf, indent=2)
171+
tf.flush()
172+
os.fsync(tf.fileno())
173+
os.replace(tf.name, output_file)
174+
175+
176+
def _print_summary(result: Dict[str, Any]) -> None:
177+
"""
178+
Print a concise summary of the result to stdout.
179+
"""
180+
181+
def count_from(field):
182+
val = result.get(field)
183+
if isinstance(val, dict):
184+
return val.get("count", 0)
185+
if isinstance(val, list):
186+
return len(val)
187+
return 0
188+
189+
print("Summary:")
190+
print(f" pointers_to_delete: {result.get('pointers_to_delete')}")
191+
print(f" ods_code_matched: {count_from('ods_code_matched')}")
192+
print(f" ods_code_mismatched:{count_from('ods_code_mismatched')}")
193+
print(f" pointer_not_found: {count_from('pointer_not_found')}")
194+
print(f" deleted_pointers: {count_from('deleted_pointers')}")
195+
print(f" failed_deletes: {count_from('failed_deletes')}")
196+
if "_output_error" in result:
197+
print(f" output_error: {result['_output_error']}")
198+
if "deletes-took-secs" in result:
199+
print(f" deletes-took-secs: {result.get('deletes-took-secs')}")
200+
201+
159202
def _delete_pointers_by_id(
160203
table_name: str,
161204
ods_code: str,
162205
pointers_to_delete: List[str] | None = None,
163206
pointers_file: str | None = None,
207+
output_file: str | None = None,
164208
) -> Dict[str, Any]:
165209
"""
166210
Delete pointers from DynamoDB table.
@@ -175,6 +219,7 @@ def _delete_pointers_by_id(
175219
- ods_code: ODS code of the organisation that the pointers belong to
176220
- pointers_to_delete: list of pointer ids to delete
177221
- pointers_file: path to JSON file (array of objects with "id" field) or text file (one id per line)
222+
- output_file: optional path to write the result JSON to (atomic replace)
178223
"""
179224
if pointers_to_delete is None and pointers_file is None:
180225
raise ValueError("Provide either pointers_to_delete or pointers_file")
@@ -187,16 +232,25 @@ def _delete_pointers_by_id(
187232
pointers_to_delete = _load_pointers_from_file(pointers_file)
188233

189234
if len(pointers_to_delete) == 0:
190-
return {
235+
result = {
191236
"pointers_to_delete": 0,
192237
"ods_code": ods_code,
193-
"ods_code_matched": [],
194-
"ods_code_mismatched": [],
195-
"pointer_not_found": [],
196-
"deleted_pointers": [],
197-
"failed_deletes": [],
238+
"ods_code_matched": {"count": 0, "ids": []},
239+
"ods_code_mismatched": {"count": 0, "ids": []},
240+
"pointer_not_found": {"count": 0, "ids": []},
241+
"deleted_pointers": {"count": 0, "ids": []},
242+
"failed_deletes": {"count": 0, "ids": []},
198243
"deletes-took-secs": 0,
199244
}
245+
if output_file:
246+
try:
247+
_write_result_file(result, output_file)
248+
except Exception as exc:
249+
result["_output_error"] = (
250+
f"Failed to write output_file {output_file}: {exc}"
251+
)
252+
_print_summary(result)
253+
return result
200254

201255
start_time = datetime.now(tz=timezone.utc)
202256

@@ -214,7 +268,7 @@ def _delete_pointers_by_id(
214268
if len(matched_pointers) == 0:
215269
print(f"None of the pointer IDs are a match for ODS code {ods_code}. Exiting.")
216270
end_time = datetime.now(tz=timezone.utc)
217-
return {
271+
result = {
218272
"pointers_to_delete": len(pointers_to_delete),
219273
"ods_code": ods_code,
220274
"ods_code_matched": {
@@ -225,20 +279,20 @@ def _delete_pointers_by_id(
225279
"count": len(mismatched_pointers),
226280
"ids": mismatched_pointers,
227281
},
228-
"pointer_not_found": {
229-
"count": 0,
230-
"ids": [],
231-
},
232-
"deleted_pointers": {
233-
"count": 0,
234-
"ids": [],
235-
},
236-
"failed_deletes": {
237-
"count": 0,
238-
"ids": [],
239-
},
282+
"pointer_not_found": {"count": 0, "ids": []},
283+
"deleted_pointers": {"count": 0, "ids": []},
284+
"failed_deletes": {"count": 0, "ids": []},
240285
"deletes-took-secs": timedelta.total_seconds(end_time - start_time),
241286
}
287+
if output_file:
288+
try:
289+
_write_result_file(result, output_file)
290+
except Exception as exc:
291+
result["_output_error"] = (
292+
f"Failed to write output_file {output_file}: {exc}"
293+
)
294+
_print_summary(result)
295+
return result
242296

243297
print(f"Checking existence of {len(matched_pointers)} pointers in {table_name}...")
244298
existing_pointers, not_found_pointers = _batch_get_existing_pointers(
@@ -252,7 +306,7 @@ def _delete_pointers_by_id(
252306
if len(existing_pointers) == 0:
253307
print("No pointers found to delete. Exiting.")
254308
end_time = datetime.now(tz=timezone.utc)
255-
return {
309+
result = {
256310
"pointers_to_delete": len(pointers_to_delete),
257311
"ods_code": ods_code,
258312
"ods_code_matched": {
@@ -267,16 +321,19 @@ def _delete_pointers_by_id(
267321
"count": len(not_found_pointers),
268322
"ids": not_found_pointers,
269323
},
270-
"deleted_pointers": {
271-
"count": 0,
272-
"ids": [],
273-
},
274-
"failed_deletes": {
275-
"count": 0,
276-
"ids": [],
277-
},
324+
"deleted_pointers": {"count": 0, "ids": []},
325+
"failed_deletes": {"count": 0, "ids": []},
278326
"deletes-took-secs": timedelta.total_seconds(end_time - start_time),
279327
}
328+
if output_file:
329+
try:
330+
_write_result_file(result, output_file)
331+
except Exception as exc:
332+
result["_output_error"] = (
333+
f"Failed to write output_file {output_file}: {exc}"
334+
)
335+
_print_summary(result)
336+
return result
280337

281338
# Proceed with deletion using BatchWriteItem
282339
pointers_deleted, failed_deletes = _batch_delete_pointers(
@@ -285,14 +342,10 @@ def _delete_pointers_by_id(
285342

286343
end_time = datetime.now(tz=timezone.utc)
287344

288-
print(" Done")
289-
return {
345+
result = {
290346
"pointers_to_delete": len(pointers_to_delete),
291347
"ods_code": ods_code,
292-
"ods_code_matched": {
293-
"count": len(matched_pointers),
294-
"ids": matched_pointers,
295-
},
348+
"ods_code_matched": {"count": len(matched_pointers), "ids": matched_pointers},
296349
"ods_code_mismatched": {
297350
"count": len(mismatched_pointers),
298351
"ids": mismatched_pointers,
@@ -301,17 +354,23 @@ def _delete_pointers_by_id(
301354
"count": len(not_found_pointers),
302355
"ids": not_found_pointers,
303356
},
304-
"deleted_pointers": {
305-
"count": len(pointers_deleted),
306-
"ids": pointers_deleted,
307-
},
308-
"failed_deletes": {
309-
"count": len(failed_deletes),
310-
"ids": failed_deletes,
311-
},
357+
"deleted_pointers": {"count": len(pointers_deleted), "ids": pointers_deleted},
358+
"failed_deletes": {"count": len(failed_deletes), "ids": failed_deletes},
312359
"deletes-took-secs": timedelta.total_seconds(end_time - start_time),
313360
}
314361

362+
if output_file:
363+
try:
364+
_write_result_file(result, output_file)
365+
except Exception as exc:
366+
result["_output_error"] = (
367+
f"Failed to write output_file {output_file}: {exc}"
368+
)
369+
370+
_print_summary(result)
371+
print(" Done")
372+
return result
373+
315374

316375
if __name__ == "__main__":
317376
fire.Fire(_delete_pointers_by_id)

0 commit comments

Comments
 (0)