Skip to content

Commit

Permalink
✨ Add Arm and Event API (#278)
Browse files Browse the repository at this point in the history
  • Loading branch information
patking02 authored Oct 31, 2023
1 parent a168006 commit 6b0d453
Show file tree
Hide file tree
Showing 16 changed files with 668 additions and 6 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ Currently, these API calls are available:

### Export

* Arms
* Data Access Groups
* Events
* Field names
* Instrument-event mapping
* File
Expand All @@ -60,7 +62,9 @@ Currently, these API calls are available:

### Import

* Arms
* Data Access Groups
* Events
* File
* Metadata
* Records
Expand All @@ -72,7 +76,9 @@ Currently, these API calls are available:

### Delete

* Arms
* Data Access Groups
* Events
* File
* Records
* Users
Expand Down
5 changes: 5 additions & 0 deletions docs/api_reference/arms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Arms

::: redcap.methods.arms
selection:
inherited_members: true
5 changes: 5 additions & 0 deletions docs/api_reference/events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Events

::: redcap.methods.events
selection:
inherited_members: true
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ nav:
- Using PyCap in an app/package: using-in-app-or-package.md
- API Reference:
- Project: api_reference/project.md
- Arms: api_reference/arms.md
- Data Access Groups: api_reference/data_access_groups.md
- Events: api_reference/events.md
- Field Names: api_reference/field_names.md
- Files: api_reference/files.md
- Instruments: api_reference/instruments.md
Expand Down
2 changes: 2 additions & 0 deletions redcap/methods/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Make the method modules available upon import"""

import redcap.methods.arms
import redcap.methods.data_access_groups
import redcap.methods.events
import redcap.methods.field_names
import redcap.methods.files
import redcap.methods.instruments
Expand Down
155 changes: 155 additions & 0 deletions redcap/methods/arms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
"""REDCap API methods for Project arms"""
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union, cast

from redcap.methods.base import Base, Json

if TYPE_CHECKING:
import pandas as pd


class Arms(Base):
"""Responsible for all API methods under 'Arms' in the API Playground"""

def export_arms(
self,
format_type: Literal["json", "csv", "xml", "df"] = "json",
arms: Optional[List[str]] = None,
):
# pylint: disable=line-too-long
"""
Export the Arms of the Project
Note:
This only works for longitudinal projects.
Args:
format_type:
Response return format
arms:
An array of arm numbers that you wish to pull arms for
(by default, all arms are pulled)
Returns:
Union[List[Dict[str, Any]], str, pandas.DataFrame]: List of Arms
Examples:
>>> proj.export_arms()
[{'arm_num': 1, 'name': 'Arm 1'}]
"""
# pylint:enable=line-too-long
payload = self._initialize_payload(content="arm", format_type=format_type)
if arms:
# Turn list of arms into dict, and append to payload
arms_dict = {f"arms[{ idx }]": arm for idx, arm in enumerate(arms)}
payload.update(arms_dict)
return_type = self._lookup_return_type(format_type, request_type="export")
response = cast(Union[Json, str], self._call_api(payload, return_type))

return self._return_data(
response=response,
content="arm",
format_type=format_type,
)

def import_arms(
self,
to_import: Union[str, List[Dict[str, Any]], "pd.DataFrame"],
return_format_type: Literal["json", "csv", "xml"] = "json",
import_format: Literal["json", "csv", "xml", "df"] = "json",
override: Optional[int] = 0,
):
"""
Import Arms into the REDCap Project
Note:
This only works for longitudinal projects.
Args:
to_import: array of dicts, csv/xml string, `pandas.DataFrame`
Note:
If you pass a csv or xml string, you should use the
`import format` parameter appropriately.
return_format_type:
Response format. By default, response will be json-decoded.
import_format:
Format of incoming data. By default, to_import will be json-encoded
override:
0 - false [default], 1 - true
You may use override=1 as a 'delete all + import' action in order to
erase all existing Arms in the project while importing new Arms.
If override=0, then you can only add new Arms or rename existing ones.
Returns:
Union[int, str]: Number of Arms added or updated
Examples:
Create a new arm
>>> new_arm = [{"arm_num": 2, "name": "Arm 2"}]
>>> proj.import_arms(new_arm)
1
"""
payload = self._initialize_import_payload(
to_import=to_import,
import_format=import_format,
return_format_type=return_format_type,
content="arm",
)
payload["action"] = "import"
payload["override"] = override

return_type = self._lookup_return_type(
format_type=return_format_type, request_type="import"
)
response = cast(Union[Json, str], self._call_api(payload, return_type))

return response

def delete_arms(
self,
arms: List[str],
return_format_type: Literal["json", "csv", "xml"] = "json",
):
"""
Delete Arms from the Project
Note:
Because of this method's destructive nature, it is only available
for use for projects in Development status.
Additionally, please be aware that deleting an arm also automatically
deletes all events that belong to that arm, and will also automatically
delete any records/data that have been collected under that arm
(this is non-reversible data loss).
This only works for longitudinal projects.
Args:
arms: List of arm numbers to delete from the project
return_format_type:
Response format. By default, response will be json-decoded.
Returns:
Union[int, str]: Number of arms deleted
Examples:
Create a new arm
>>> new_arm = [{"arm_num": 2, "name": "Arm 2"}]
>>> proj.import_arms(new_arm)
1
Delete the new arm
>>> proj.delete_arms([2])
1
"""
payload = self._initialize_payload(
content="arm", return_format_type=return_format_type
)
payload["action"] = "delete"
# Turn list of arms into dict, and append to payload
arms_dict = {f"arms[{ idx }]": arm for idx, arm in enumerate(arms)}
payload.update(arms_dict)

return_type = self._lookup_return_type(
format_type=return_format_type, request_type="delete"
)
response = cast(Union[Json, str], self._call_api(payload, return_type))

return response
2 changes: 2 additions & 0 deletions redcap/methods/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,9 @@ def _return_data(
self,
response: Union[Json, str],
content: Literal[
"arm",
"dag",
"event",
"exportFieldNames",
"formEventMapping",
"log",
Expand Down
155 changes: 155 additions & 0 deletions redcap/methods/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
"""REDCap API methods for Project events"""
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union, cast

from redcap.methods.base import Base, Json

if TYPE_CHECKING:
import pandas as pd


class Events(Base):
"""Responsible for all API methods under 'Events' in the API Playground"""

def export_events(
self,
format_type: Literal["json", "csv", "xml", "df"] = "json",
arms: Optional[List[str]] = None,
):
# pylint: disable=line-too-long
"""
Export the Events of the Project
Note:
This only works for longitudinal projects.
Args:
format_type:
Response return format
arms:
An array of arm numbers that you wish to pull events for
(by default, all events are pulled)
Returns:
Union[List[Dict[str, Any]], str, pandas.DataFrame]: List of Events
Examples:
>>> proj.export_events()
[{'event_name': 'Event 1', 'arm_num': 1, 'unique_event_name': 'event_1_arm_1',
'custom_event_label': '', 'event_id': ...}, {'event_name': 'Event 2', ...}]
"""
# pylint:enable=line-too-long
payload = self._initialize_payload(content="event", format_type=format_type)
if arms:
# Turn list of arms into dict, and append to payload
arms_dict = {f"arms[{ idx }]": arm for idx, arm in enumerate(arms)}
payload.update(arms_dict)
return_type = self._lookup_return_type(format_type, request_type="export")
response = cast(Union[Json, str], self._call_api(payload, return_type))

return self._return_data(
response=response,
content="event",
format_type=format_type,
)

def import_events(
self,
to_import: Union[str, List[Dict[str, Any]], "pd.DataFrame"],
return_format_type: Literal["json", "csv", "xml"] = "json",
import_format: Literal["json", "csv", "xml", "df"] = "json",
override: Optional[int] = 0,
):
"""
Import Events into the REDCap Project
Note:
This only works for longitudinal projects.
Args:
to_import: array of dicts, csv/xml string, `pandas.DataFrame`
Note:
If you pass a csv or xml string, you should use the
`import format` parameter appropriately.
return_format_type:
Response format. By default, response will be json-decoded.
import_format:
Format of incoming data. By default, to_import will be json-encoded
override:
0 - false [default], 1 - true
You may use override=1 as a 'delete all + import' action in order to
erase all existing Events in the project while importing new Events.
If override=0, then you can only add new Events or rename existing ones.
Returns:
Union[int, str]: Number of Events added or updated
Examples:
Create a new event
>>> new_event = [{"event_name": "Event 2", "arm_num": "1"}]
>>> proj.import_events(new_event)
1
"""
payload = self._initialize_import_payload(
to_import=to_import,
import_format=import_format,
return_format_type=return_format_type,
content="event",
)
payload["action"] = "import"
payload["override"] = override

return_type = self._lookup_return_type(
format_type=return_format_type, request_type="import"
)
response = cast(Union[Json, str], self._call_api(payload, return_type))

return response

def delete_events(
self,
events: List[str],
return_format_type: Literal["json", "csv", "xml"] = "json",
):
"""
Delete Events from the Project
Note:
Because of this method's destructive nature, it is only available
for use for projects in Development status.
Additionally, please be aware that deleting an event will automatically
delete any records/data that have been collected under that event
(this is non-reversible data loss).
This only works for longitudinal projects.
Args:
events: List of unique event names to delete from the project
return_format_type:
Response format. By default, response will be json-decoded.
Returns:
Union[int, str]: Number of events deleted
Examples:
Create a new event
>>> new_event = [{"event_name": "Event 2", "arm_num": "1"}]
>>> proj.import_events(new_event)
1
Delete the new event
>>> proj.delete_events(["event_2_arm_1"])
1
"""
payload = self._initialize_payload(
content="event", return_format_type=return_format_type
)
payload["action"] = "delete"
# Turn list of events into dict, and append to payload
events_dict = {f"events[{ idx }]": event for idx, event in enumerate(events)}
payload.update(events_dict)

return_type = self._lookup_return_type(
format_type=return_format_type, request_type="delete"
)
response = cast(Union[Json, str], self._call_api(payload, return_type))

return response
4 changes: 2 additions & 2 deletions redcap/methods/user_roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ def export_user_roles(
Examples:
>>> proj.export_user_roles()
[{'unique_role_name': ..., 'role_label': 'Test role', 'design': '0', 'user_rights': '0',
'data_access_groups': '0', 'reports': '0', 'stats_and_charts': '0',
[{'unique_role_name': ..., 'role_label': 'Test role', 'design': '0', 'alerts': '0',
'user_rights': '0', 'data_access_groups': '0', 'reports': '0', 'stats_and_charts': '0',
'manage_survey_participants': '0', 'calendar': '0', 'data_import_tool': '0',
'data_comparison_tool': '0', 'logging': '0', 'file_repository': '0',
'data_quality_create': '0', 'data_quality_execute': '0', 'api_export': '0',
Expand Down
Loading

0 comments on commit 6b0d453

Please sign in to comment.