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 @@ -54,6 +54,8 @@ Currently, these API calls are available:
* Survey participant list
* Users
* User-DAG assignment
* User Roles
* User-Role assignment
* Version

### Import
Expand All @@ -65,6 +67,8 @@ Currently, these API calls are available:
* Repeating instruments and events
* Users
* User-DAG assignment
* User Roles
* User-Role assignment

### Delete

Expand Down
5 changes: 5 additions & 0 deletions docs/api_reference/user_roles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# User Roles

::: redcap.methods.user_roles
selection:
inherited_members: true
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ nav:
- Reports: api_reference/reports.md
- Surveys: api_reference/surveys.md
- Users: api_reference/users.md
- User Roles: api_reference/user_roles.md
- Version: api_reference/version.md
theme:
name: material
Expand Down
1 change: 1 addition & 0 deletions redcap/methods/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@
import redcap.methods.reports
import redcap.methods.surveys
import redcap.methods.users
import redcap.methods.user_roles
import redcap.methods.version
241 changes: 241 additions & 0 deletions redcap/methods/user_roles.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
"""REDCap API methods for Project user roles"""
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union, overload

from redcap.methods.base import Base, Json

if TYPE_CHECKING:
import pandas as pd


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

@overload
def export_user_roles(self, format_type: Literal["json"], df_kwargs: None) -> Json:
...

@overload
def export_user_roles(
self, format_type: Literal["csv", "xml"], df_kwargs: None
) -> str:
...

@overload
def export_user_roles(
self, format_type: Literal["df"], df_kwargs: Optional[Dict[str, Any]]
) -> "pd.DataFrame":
...

def export_user_roles(
self,
format_type: Literal["json", "csv", "xml", "df"] = "json",
df_kwargs: Optional[Dict[str, Any]] = None,
):
"""
Export the user roles of the Project

Args:
format_type:
Response return format
df_kwargs:
Passed to `pandas.read_csv` to control construction of
returned DataFrame. By default, nothing

Returns:
Union[List[Dict[str, Any]], str, pandas.DataFrame]:
List of user roles with assigned user rights

Examples:
>>> proj.export_user_roles()
[{'unique_role_name': ..., 'role_label': 'Test role', 'design': '0', 'user_rights': '0',
'data_access_groups': '0', 'data_export': '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',
'api_import': '0', 'mobile_app': '0', 'mobile_app_download_data': '0',
'record_create': '0', 'record_rename': '0', 'record_delete': '0',
'lock_records_customization': '0', 'lock_records': '0', 'lock_records_all_forms': '0',
'forms': {'form_1': 2}}]
"""
payload = self._initialize_payload(content="userRole", format_type=format_type)
return_type = self._lookup_return_type(format_type, request_type="export")
response = self._call_api(payload, return_type)

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

@overload
def import_user_roles(
self,
to_import: Union[str, List[Dict[str, Any]], "pd.DataFrame"],
return_format_type: Literal["json"],
import_format: Literal["json", "csv", "xml", "df"] = "json",
) -> int:
...

@overload
def import_user_roles(
self,
to_import: Union[str, List[Dict[str, Any]], "pd.DataFrame"],
return_format_type: Literal["csv", "xml"],
import_format: Literal["json", "csv", "xml", "df"] = "json",
) -> str:
...

def import_user_roles(
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",
):
"""
Import user roles into the REDCap Project

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

Returns:
Union[int, str]: Number of user roles added or updated

Examples:
>>> roles = proj.export_user_roles()
>>> proj.import_user_roles(roles)
1
"""
payload = self._initialize_import_payload(
to_import=to_import,
import_format=import_format,
return_format_type=return_format_type,
content="userRole",
)

return_type = self._lookup_return_type(
format_type=return_format_type, request_type="import"
)
response = self._call_api(payload, return_type)

return response

@overload
def export_user_role_assignment(
self, format_type: Literal["json"], df_kwargs: None
) -> Json:
...

@overload
def export_user_role_assignment(
self, format_type: Literal["csv", "xml"], df_kwargs: None
) -> str:
...

@overload
def export_user_role_assignment(
self, format_type: Literal["df"], df_kwargs: Optional[Dict[str, Any]]
) -> "pd.DataFrame":
...

def export_user_role_assignment(
self,
format_type: Literal["json", "csv", "xml", "df"] = "json",
df_kwargs: Optional[Dict[str, Any]] = None,
):
"""
Export the User-Role assignments of the Project

Args:
format_type:
Response return format
df_kwargs:
Passed to `pandas.read_csv` to control construction of
returned DataFrame. By default, nothing

Returns:
Union[List[Dict[str, Any]], str, pandas.DataFrame]:
List of user-role assignments

Examples:
>>> proj.export_user_role_assignment()
[{'username': ..., 'unique_role_name': ''}]
"""
payload = self._initialize_payload(
content="userRoleMapping", format_type=format_type
)
return_type = self._lookup_return_type(format_type, request_type="export")
response = self._call_api(payload, return_type)

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

@overload
def import_user_role_assignment(
self,
to_import: Union[str, List[Dict[str, Any]], "pd.DataFrame"],
return_format_type: Literal["json"],
import_format: Literal["json", "csv", "xml", "df"] = "json",
) -> int:
...

@overload
def import_user_role_assignment(
self,
to_import: Union[str, List[Dict[str, Any]], "pd.DataFrame"],
return_format_type: Literal["csv", "xml"],
import_format: Literal["json", "csv", "xml", "df"] = "json",
) -> str:
...

def import_user_role_assignment(
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",
):
"""
Import User-Role assignments into the REDCap Project

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

Returns:
Union[int, str]: Number of user-role assignments added or updated

Examples:
>>> user_role_assignments = proj.export_user_role_assignment()
>>> proj.import_user_role_assignment(user_role_assignments)
1
"""
payload = self._initialize_import_payload(
to_import=to_import,
import_format=import_format,
return_format_type=return_format_type,
content="userRoleMapping",
)

return_type = self._lookup_return_type(
format_type=return_format_type, request_type="import"
)
response = self._call_api(payload, return_type)

return response
6 changes: 6 additions & 0 deletions redcap/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Project(
methods.reports.Reports,
methods.surveys.Surveys,
methods.users.Users,
methods.user_roles.UserRoles,
methods.version.Version,
):
"""Main class for interacting with REDCap projects
Expand Down Expand Up @@ -71,5 +72,10 @@ def redcap_version(self) -> Optional[semantic_version.Version]:
try:
return self._redcap_version
except AttributeError:
# weird pylint bug on windows where it can't find Version.export_version()
# possible too many parents it's inheriting from? We also need to disable
# useless-supression since this is a windows only issue
# pylint: disable=no-member,useless-suppression
self._redcap_version = self.export_version()
# pylint: enable=no-member,useless-suppression
return self._redcap_version
3 changes: 3 additions & 0 deletions tests/data/doctest_project.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
<p>Thank you!</p>" offline_instructions="" acknowledgement="<p><strong>Thank you for taking the survey.</strong></p>
<p>Have a nice day!</p>" stop_action_acknowledgement="" stop_action_delete_response="0" question_by_section="0" display_page_number="0" question_auto_numbering="1" survey_enabled="1" save_and_return="0" save_and_return_code_bypass="0" logo="" hide_title="0" view_results="0" min_responses_view_results="10" check_diversity_view_results="0" end_survey_redirect_url="" survey_expiration="" promis_skip_question="0" survey_auth_enabled_single="0" edit_completed_response="0" hide_back_button="0" show_required_field_text="1" confirmation_email_subject="" confirmation_email_content="" confirmation_email_from="" confirmation_email_from_display="" confirmation_email_attach_pdf="0" confirmation_email_attachment="" text_to_speech="0" text_to_speech_language="en" end_survey_redirect_next_survey="0" end_survey_redirect_next_survey_logic="" theme="" text_size="1" font_family="16" theme_text_buttons="" theme_bg_page="" theme_text_title="" theme_bg_title="" theme_text_sectionheader="" theme_bg_sectionheader="" theme_text_question="" theme_bg_question="" enhanced_choices="0" repeat_survey_enabled="0" repeat_survey_btn_text="" repeat_survey_btn_location="HIDDEN" response_limit="" response_limit_include_partials="1" response_limit_custom_text="<p>Thank you for your interest; however, the survey is closed because the maximum number of responses has been reached.</p>" survey_time_limit_days="" survey_time_limit_hours="" survey_time_limit_minutes="" email_participant_field="" end_of_survey_pdf_download="0" pdf_save_to_field="" pdf_save_to_event_id="" pdf_auto_archive="0" pdf_econsent_version="" pdf_econsent_type="" pdf_econsent_firstname_field="" pdf_econsent_firstname_event_id="" pdf_econsent_lastname_field="" pdf_econsent_lastname_event_id="" pdf_econsent_dob_field="" pdf_econsent_dob_event_id="" pdf_econsent_allow_edit="1" pdf_econsent_signature_field1="" pdf_econsent_signature_field2="" pdf_econsent_signature_field3="" pdf_econsent_signature_field4="" pdf_econsent_signature_field5=""/>
</redcap:SurveysGroup>
<redcap:UserRolesGroup>
<redcap:UserRoles role_name="Test role" unique_role_name="U-942MC93HH8" lock_record="0" lock_record_multiform="0" lock_record_customize="0" data_export_tool="0" data_import_tool="0" data_comparison_tool="0" data_logging="0" file_repository="0" double_data="0" user_rights="0" data_access_groups="0" graphical="0" reports="0" design="0" calendar="0" data_entry="[form_1,2]" api_export="0" api_import="0" mobile_app="0" mobile_app_download_data="0" record_create="0" record_rename="0" record_delete="0" dts="0" participants="0" data_quality_design="0" data_quality_execute="0" data_quality_resolution="1" random_setup="0" random_dashboard="0" random_perform="0" realtime_webservice_mapping="0" realtime_webservice_adjudicate="0" external_module_config=""/>
</redcap:UserRolesGroup>
<redcap:ReportsGroup>
<redcap:Reports title="Example Report" unique_report_name="R-139MKP9FXE" report_order="1" user_access="ALL" user_edit_access="ALL" description="&lt;p&gt;Example report for testing purposes&lt;/p&gt;" combine_checkbox_values="0" output_dags="0" output_survey_fields="0" output_missing_data_codes="0" remove_line_breaks_in_values="1" orderby_field1="record_id" orderby_sort1="ASC" orderby_field2="" orderby_sort2="" orderby_field3="" orderby_sort3="" advanced_logic=" ([event-name][field_1] = &quot;1&quot;)" filter_type="RECORD" dynamic_filter1="" dynamic_filter2="" dynamic_filter3="" hash="WNPAJ7NX8C8PCW9C" short_url="" is_public="0" report_display_include_repeating_fields="1" report_display_header="BOTH" report_display_data="BOTH" limiter_logic=" ([event-name][field_1] = &quot;1&quot;)" redcap_reports_fields="record_id,checkbox_field" redcap_reports_filter_dags="" redcap_reports_filter_events="" ID="ab5eeefea8211d227c70dc0cabc58f28fa095eda"/>
</redcap:ReportsGroup>
Expand Down
3 changes: 3 additions & 0 deletions tests/data/test_simple_project.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
<redcap:DataAccessGroupsGroup>
<redcap:DataAccessGroups group_name="Test DAG"/>
</redcap:DataAccessGroupsGroup>
<redcap:UserRolesGroup>
<redcap:UserRoles role_name="Example Role" unique_role_name="U-573AEYRNR8" lock_record="0" lock_record_multiform="0" lock_record_customize="0" data_export_tool="2" data_import_tool="0" data_comparison_tool="0" data_logging="0" file_repository="1" double_data="0" user_rights="0" data_access_groups="0" graphical="1" reports="1" design="0" calendar="1" data_entry="[demographics,1]" api_export="0" api_import="0" mobile_app="0" mobile_app_download_data="0" record_create="1" record_rename="0" record_delete="0" dts="0" participants="1" data_quality_design="0" data_quality_execute="0" data_quality_resolution="1" random_setup="0" random_dashboard="0" random_perform="0" realtime_webservice_mapping="0" realtime_webservice_adjudicate="0" external_module_config=""/>
</redcap:UserRolesGroup>
<redcap:MultilanguageSettingsGroup>
<redcap:MultilanguageSettings settings="YToxNDp7czo3OiJ2ZXJzaW9uIjtzOjY6IjEyLjEuMCI7czo1OiJsYW5ncyI7YTowOnt9czo5OiJwcm9qZWN0SWQiO3M6NToiMzk1NTAiO3M6MTU6ImRlc2lnbmF0ZWRGaWVsZCI7czowOiIiO3M6NToiZGVidWciO2I6MDtzOjc6InJlZkxhbmciO3M6MDoiIjtzOjEyOiJmYWxsYmFja0xhbmciO3M6MDoiIjtzOjg6ImRpc2FibGVkIjtiOjA7czoyNToiaGlnaGxpZ2h0TWlzc2luZ0RhdGFlbnRyeSI7YjowO3M6MjI6ImhpZ2hsaWdodE1pc3NpbmdTdXJ2ZXkiO2I6MDtzOjEyOiJhbGVydFNvdXJjZXMiO2E6MDp7fXM6MTQ6ImV4Y2x1ZGVkQWxlcnRzIjthOjA6e31zOjE0OiJleGNsdWRlZEZpZWxkcyI7YTowOnt9czoxNjoiZXhjbHVkZWRTZXR0aW5ncyI7YTowOnt9fQ=="/>
</redcap:MultilanguageSettingsGroup>
Expand Down
31 changes: 31 additions & 0 deletions tests/integration/test_simple_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,37 @@ def test_export_users(simple_project):
assert users[0]["api_export"] == 1


@pytest.mark.integration
def test_export_user_roles(simple_project):
user_roles = simple_project.export_user_roles()
assert len(user_roles) == 1
assert user_roles[0]["role_label"] == "Example Role"


@pytest.mark.integration
def test_export_import_user_role_assignments(simple_project):
new_user = "pandeharris@gmail.com"
simple_project.import_users([{"username": new_user}])

example_role_name = simple_project.export_user_roles()[0]["unique_role_name"]

res = simple_project.import_user_role_assignment(
[{"username": new_user, "unique_role_name": example_role_name}]
)
assert res == 1

user_role_assignments = simple_project.export_user_role_assignment()
test_user_role_name = [
user_role["unique_role_name"]
for user_role in user_role_assignments
if user_role["username"] == new_user
][0]
assert test_user_role_name == example_role_name
# cleanup
res = simple_project.delete_users([new_user])
assert res == 1


@pytest.mark.integration
def test_export_dags(simple_project):
dags = simple_project.export_dags(format_type="df")
Expand Down
Loading