Skip to content

Commit 0b2db5f

Browse files
committed
⚙️ Add functionality to delete_records
1 parent ca4bdd7 commit 0b2db5f

File tree

4 files changed

+121
-0
lines changed

4 files changed

+121
-0
lines changed

redcap/methods/records.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,13 +330,41 @@ def import_records(
330330
def delete_records(
331331
self,
332332
records: List[str],
333+
arm: Optional[str] = None,
334+
instrument: Optional[str] = None,
335+
event: Optional[str] = None,
336+
repeat_instance: Optional[int] = None,
337+
delete_logging: bool = False,
333338
return_format_type: Literal["json", "csv", "xml"] = "json",
334339
):
340+
# pylint: disable=line-too-long
335341
"""
336342
Delete records from the project.
337343
338344
Args:
339345
records: List of record IDs to delete from the project
346+
arm:
347+
the arm number of the arm in which the record(s) should be deleted.
348+
(This can only be used if the project is longitudinal with more than one arm.)
349+
NOTE: If the arm parameter is not provided, the specified records will be
350+
deleted from all arms in which they exist. Whereas, if arm is provided,
351+
they will only be deleted from the specified arm.
352+
instrument:
353+
the unique instrument name (column B in the Data Dictionary) of an
354+
instrument (as a string) if you wish to delete the data for all fields
355+
on the specified instrument for the records specified.
356+
event:
357+
the unique event name - only for longitudinal projects. NOTE: If
358+
instrument is provided for a longitudinal project, the event parameter
359+
is mandatory.
360+
repeat_instance:
361+
the repeating instance number for a repeating instrument or repeating event.
362+
NOTE: If project has repeating instruments/events, it will remove only the
363+
data for that repeating instance.
364+
delete_logging:
365+
provide a value of False ("keep logging") or True ("delete logging"). This
366+
activity when deleting the record?" setting enabled by an administrator on
367+
the Edit Project Settings page. The default value for PyCap is False
340368
return_format_type:
341369
Response format. By default, response will be json-decoded.
342370
@@ -352,11 +380,38 @@ def delete_records(
352380
{'count': 2}
353381
>>> proj.delete_records(["3", "4"])
354382
2
383+
>>> new_record = [
384+
... {"record_id": 3, "redcap_event_name": "event_1_arm_1", "redcap_repeat_instance": 1, "field_1": 1,},
385+
... {"record_id": 3, "redcap_event_name": "event_1_arm_1", "redcap_repeat_instance": 2, "field_1": 0,},
386+
... ]
387+
>>> proj.import_records(new_record)
388+
{'count': 1}
389+
>>> proj.delete_records(records=["3"], event="event_1_arm_1", repeat_instance=2)
390+
1
391+
>>> proj.export_records(records=["3"])
392+
[{'record_id': '3', 'redcap_event_name': 'event_1_arm_1', 'redcap_repeat_instrument': '', 'redcap_repeat_instance': 1,
393+
'field_1': '1', 'checkbox_field___1': '0', 'checkbox_field___2': '0', 'upload_field': '', 'form_1_complete': '0'}]
394+
>>> proj.delete_records(records=["3"])
395+
1
355396
"""
397+
# pylint: enable=line-too-long
356398
payload = self._initialize_payload(
357399
content="record", return_format_type=return_format_type
358400
)
359401
payload["action"] = "delete"
402+
if delete_logging:
403+
payload["delete_logging"] = "1"
404+
else:
405+
payload["delete_logging"] = "0"
406+
407+
if arm:
408+
payload["arm"] = arm
409+
if instrument:
410+
payload["instrument"] = instrument
411+
if event:
412+
payload["event"] = event
413+
if repeat_instance:
414+
payload["repeat_instance"] = repeat_instance
360415
# Turn list of records into dict, and append to payload
361416
records_dict = {
362417
f"records[{ idx }]": record for idx, record in enumerate(records)

tests/integration/test_long_project.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,38 @@ def test_limit_export_records_forms_and_fields(long_project):
139139
assert complete_cols == ["baseline_data_complete"]
140140

141141

142+
def test_delete_records_from_one_instrument_only(long_project):
143+
# Add new record to test partial deletion
144+
new_record = [
145+
{
146+
"study_id": "3",
147+
"redcap_event_name": "enrollment_arm_1",
148+
"redcap_repeat_instrument": "",
149+
"redcap_repeat_instance": "",
150+
},
151+
{
152+
"study_id": "3",
153+
"redcap_event_name": "visit_1_arm_1",
154+
"redcap_repeat_instrument": "",
155+
"redcap_repeat_instance": "",
156+
},
157+
]
158+
res = long_project.import_records(new_record)
159+
assert res["count"] == 1
160+
161+
res = long_project.export_records(records=["3"])
162+
assert len(res) == 2
163+
164+
res = long_project.delete_records(records=["3"], event="visit_1_arm_1")
165+
assert res == 1
166+
167+
res = long_project.export_records(records=["3"])
168+
assert len(res) == 1
169+
# restore project to original state pre-test
170+
res = long_project.delete_records(["3"])
171+
assert res == 1
172+
173+
142174
@pytest.mark.integration
143175
def test_arms_export(long_project):
144176
response = long_project.export_arms()

tests/unit/callback_utils.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,23 @@ def handle_simple_project_delete_records(data: dict) -> int:
506506
return resp
507507

508508

509+
def handle_long_project_delete_records(data: dict) -> int:
510+
"""Given long project delete request, determine how many records were deleted"""
511+
resp = 0
512+
assert data["arm"]
513+
assert data["instrument"]
514+
assert data["event"]
515+
assert data["repeat_instance"]
516+
assert data["repeat_instance"]
517+
assert data["delete_logging"]
518+
519+
for key in data:
520+
if "records[" in key:
521+
resp += 1
522+
523+
return resp
524+
525+
509526
def handle_simple_project_import_records(data: dict) -> dict:
510527
"""Given simple project import request, determine response"""
511528
resp = {"count": 2}
@@ -572,6 +589,10 @@ def handle_long_project_records_request(**kwargs) -> Any:
572589
# if the None value gets returned it means the test failed
573590
resp = None
574591
headers = kwargs["headers"]
592+
if "delete" in data.get("action", "other"):
593+
resp = handle_long_project_delete_records(data)
594+
595+
return (201, headers, json.dumps(resp))
575596
# data import
576597
if "returnContent" in data:
577598
resp = {"count": 1}

tests/unit/test_long_project.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,19 @@ def test_export_with_events(long_project):
149149
assert isinstance(record, dict)
150150

151151

152+
def test_delete_records_from_event(long_project):
153+
res = long_project.delete_records(
154+
records=["1"],
155+
arm="1",
156+
instrument="form_1",
157+
event="enrollment_arm_1",
158+
repeat_instance=1,
159+
delete_logging=True,
160+
)
161+
162+
assert res == 1
163+
164+
152165
def test_fem_export(long_project):
153166
fem = long_project.export_instrument_event_mappings(format_type="json")
154167

0 commit comments

Comments
 (0)