Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Arm and Event API #278

Merged
merged 15 commits into from
Oct 31, 2023
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
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
Loading