Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ Currently, these API calls are available:
* Records
* Repeating instruments and events
* Report
* Survey link
* Survey access code
* Survey participant list
* Survey queue link
* Survey return code
* Users
* User-DAG assignment
* User Roles
Expand Down
55 changes: 55 additions & 0 deletions redcap/methods/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,13 +330,41 @@ def import_records(
def delete_records(
self,
records: List[str],
arm: Optional[str] = None,
instrument: Optional[str] = None,
event: Optional[str] = None,
repeat_instance: Optional[int] = None,
delete_logging: bool = False,
return_format_type: Literal["json", "csv", "xml"] = "json",
):
# pylint: disable=line-too-long
"""
Delete records from the project.

Args:
records: List of record IDs to delete from the project
arm:
the arm number of the arm in which the record(s) should be deleted.
(This can only be used if the project is longitudinal with more than one arm.)
NOTE: If the arm parameter is not provided, the specified records will be
deleted from all arms in which they exist. Whereas, if arm is provided,
they will only be deleted from the specified arm.
instrument:
the unique instrument name (column B in the Data Dictionary) of an
instrument (as a string) if you wish to delete the data for all fields
on the specified instrument for the records specified.
event:
the unique event name - only for longitudinal projects. NOTE: If
instrument is provided for a longitudinal project, the event parameter
is mandatory.
repeat_instance:
the repeating instance number for a repeating instrument or repeating event.
NOTE: If project has repeating instruments/events, it will remove only the
data for that repeating instance.
delete_logging:
provide a value of False ("keep logging") or True ("delete logging"). This
activity when deleting the record?" setting enabled by an administrator on
the Edit Project Settings page. The default value for PyCap is False
return_format_type:
Response format. By default, response will be json-decoded.

Expand All @@ -352,11 +380,38 @@ def delete_records(
{'count': 2}
>>> proj.delete_records(["3", "4"])
2
>>> new_record = [
... {"record_id": 3, "redcap_event_name": "event_1_arm_1", "redcap_repeat_instance": 1, "field_1": 1,},
... {"record_id": 3, "redcap_event_name": "event_1_arm_1", "redcap_repeat_instance": 2, "field_1": 0,},
... ]
>>> proj.import_records(new_record)
{'count': 1}
>>> proj.delete_records(records=["3"], event="event_1_arm_1", repeat_instance=2)
1
>>> proj.export_records(records=["3"])
[{'record_id': '3', 'redcap_event_name': 'event_1_arm_1', 'redcap_repeat_instrument': '', 'redcap_repeat_instance': 1,
'field_1': '1', 'checkbox_field___1': '0', 'checkbox_field___2': '0', 'upload_field': '', 'form_1_complete': '0'}]
>>> proj.delete_records(records=["3"])
1
"""
# pylint: enable=line-too-long
payload = self._initialize_payload(
content="record", return_format_type=return_format_type
)
payload["action"] = "delete"
if delete_logging:
payload["delete_logging"] = "1"
else:
payload["delete_logging"] = "0"

if arm:
payload["arm"] = arm
if instrument:
payload["instrument"] = instrument
if event:
payload["event"] = event
if repeat_instance:
payload["repeat_instance"] = repeat_instance
# Turn list of records into dict, and append to payload
records_dict = {
f"records[{ idx }]": record for idx, record in enumerate(records)
Expand Down
32 changes: 32 additions & 0 deletions tests/integration/test_long_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,38 @@ def test_limit_export_records_forms_and_fields(long_project):
assert complete_cols == ["baseline_data_complete"]


def test_delete_records_from_one_instrument_only(long_project):
# Add new record to test partial deletion
new_record = [
{
"study_id": "3",
"redcap_event_name": "enrollment_arm_1",
"redcap_repeat_instrument": "",
"redcap_repeat_instance": "",
},
{
"study_id": "3",
"redcap_event_name": "visit_1_arm_1",
"redcap_repeat_instrument": "",
"redcap_repeat_instance": "",
},
]
res = long_project.import_records(new_record)
assert res["count"] == 1

res = long_project.export_records(records=["3"])
assert len(res) == 2

res = long_project.delete_records(records=["3"], event="visit_1_arm_1")
assert res == 1

res = long_project.export_records(records=["3"])
assert len(res) == 1
# restore project to original state pre-test
res = long_project.delete_records(["3"])
assert res == 1


@pytest.mark.integration
def test_arms_export(long_project):
response = long_project.export_arms()
Expand Down
21 changes: 21 additions & 0 deletions tests/unit/callback_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,23 @@ def handle_simple_project_delete_records(data: dict) -> int:
return resp


def handle_long_project_delete_records(data: dict) -> int:
"""Given long project delete request, determine how many records were deleted"""
resp = 0
assert data["arm"]
assert data["instrument"]
assert data["event"]
assert data["repeat_instance"]
assert data["repeat_instance"]
assert data["delete_logging"]

for key in data:
if "records[" in key:
resp += 1

return resp


def handle_simple_project_import_records(data: dict) -> dict:
"""Given simple project import request, determine response"""
resp = {"count": 2}
Expand Down Expand Up @@ -572,6 +589,10 @@ def handle_long_project_records_request(**kwargs) -> Any:
# if the None value gets returned it means the test failed
resp = None
headers = kwargs["headers"]
if "delete" in data.get("action", "other"):
resp = handle_long_project_delete_records(data)

return (201, headers, json.dumps(resp))
# data import
if "returnContent" in data:
resp = {"count": 1}
Expand Down
13 changes: 13 additions & 0 deletions tests/unit/test_long_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,19 @@ def test_export_with_events(long_project):
assert isinstance(record, dict)


def test_delete_records_from_event(long_project):
res = long_project.delete_records(
records=["1"],
arm="1",
instrument="form_1",
event="enrollment_arm_1",
repeat_instance=1,
delete_logging=True,
)

assert res == 1


def test_fem_export(long_project):
fem = long_project.export_instrument_event_mappings(format_type="json")

Expand Down