Skip to content

Commit

Permalink
🤝 Resolve conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
pwildenhain committed Mar 29, 2022
2 parents e275376 + 433449d commit 171411c
Show file tree
Hide file tree
Showing 20 changed files with 404 additions and 226 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ jobs:
poetry install -E data_science
- name: Run doctests
# Forks can't run doctests, requires super user token
if: github.repository == 'redcap-tools/PyCap'
if: github.actor == 'pwildenhain'
run: |
poetry run pytest --doctest-only
poetry run pytest --doctest-only --doctest-plus
- name: Run tests
run: |
poetry run pytest
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Currently, these API calls are available:

* Field names
* Instrument-event mapping
* Repeating instruments and events
* File
* Metadata
* Project Info
Expand All @@ -57,6 +58,7 @@ Currently, these API calls are available:
* File
* Metadata
* Records
* Repeating instruments and events

### Delete

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

::: redcap.methods.repeating
selection:
inherited_members: true
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ nav:
- Instruments: api_reference/instruments.md
- Metadata: api_reference/metadata.md
- Project Info: api_reference/project_info.md
- Repeating: api_reference/repeating.md
- Records: api_reference/records.md
- Reports: api_reference/reports.md
- Surveys: api_reference/surveys.md
Expand Down
349 changes: 175 additions & 174 deletions poetry.lock

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ mkdocs = "^1.2.3"
mkdocs-material = "^8.1.3"
mkdocstrings = "^0.17.0"
pytest-doctestplus = "^0.11.2"
typing-extensions = "^4.0.1"

[tool.poetry.extras]
data_science = ["pandas"]
Expand Down
1 change: 0 additions & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
[pytest]
doctest_optionflags = NORMALIZE_WHITESPACE ELLIPSIS FAIL_FAST REPORT_NDIFF
doctest_plus = enabled
addopts = -rsxX -l --tb=short --strict --pylint --black --cov=redcap --cov-report=xml
markers =
integration: test connects to redcapdemo.vanderbilt.edu server
Expand Down
1 change: 1 addition & 0 deletions redcap/methods/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import redcap.methods.metadata
import redcap.methods.project_info
import redcap.methods.records
import redcap.methods.repeating
import redcap.methods.reports
import redcap.methods.surveys
import redcap.methods.users
Expand Down
33 changes: 14 additions & 19 deletions redcap/methods/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ def _initialize_import_payload(
to_import: List[dict],
import_format: Literal["json"],
return_format_type: Literal["json", "csv", "xml"],
data_type: Literal["record", "metadata"],
content: str,
) -> Dict[str, Any]:
...

Expand All @@ -265,7 +265,7 @@ def _initialize_import_payload(
to_import: str,
import_format: Literal["csv", "xml"],
return_format_type: Literal["json", "csv", "xml"],
data_type: Literal["record", "metadata"],
content: str,
) -> Dict[str, Any]:
...

Expand All @@ -275,7 +275,7 @@ def _initialize_import_payload(
to_import: "pd.DataFrame",
import_format: Literal["df"],
return_format_type: Literal["json", "csv", "xml"],
data_type: Literal["record", "metadata"],
content: str,
) -> Dict[str, Any]:
...

Expand All @@ -284,32 +284,27 @@ def _initialize_import_payload(
to_import: Union[List[dict], str, "pd.DataFrame"],
import_format: Literal["json", "csv", "xml", "df"],
return_format_type: Literal["json", "csv", "xml"],
data_type: Literal["record", "metadata"],
content: str,
):
"""Standardize the data to be imported and add it to the payload
Args:
to_import: array of dicts, csv/xml string, ``pandas.DataFrame``
import_format: Format of incoming data.
data_type: The kind of data that are imported
import_format: Format of incoming data
return_format_type: Format of outgoing (returned) data
content: The kind of data that are imported
Returns:
payload: The initialized payload dictionary and updated format
"""

payload = self._initialize_payload(
content=data_type, return_format_type=return_format_type
content=content, return_format_type=return_format_type
)
if import_format == "df":
buf = StringIO()
if data_type == "record":
if self.is_longitudinal:
csv_kwargs = {"index_label": [self.def_field, "redcap_event_name"]}
else:
csv_kwargs = {"index_label": self.def_field}
elif data_type == "metadata":
csv_kwargs = {"index_label": "field_name"}
to_import.to_csv(buf, **csv_kwargs)
has_named_index = to_import.index.name is not None
to_import.to_csv(buf, index=has_named_index)
payload["data"] = buf.getvalue()
buf.close()
import_format = "csv"
Expand Down Expand Up @@ -425,10 +420,7 @@ def _return_data(
return response

if not df_kwargs:
if (
content in ["formEventMapping", "participantList", "project", "user"]
or record_type == "eav"
):
if record_type == "eav":
df_kwargs = {}
elif content == "exportFieldNames":
df_kwargs = {"index_col": "original_field_name"}
Expand All @@ -439,6 +431,9 @@ def _return_data(
df_kwargs = {"index_col": [self.def_field, "redcap_event_name"]}
else:
df_kwargs = {"index_col": self.def_field}
# catchall for other endpoints
else:
df_kwargs = {}

buf = StringIO(response)
dataframe = self._read_csv(buf, **df_kwargs)
Expand Down
4 changes: 2 additions & 2 deletions redcap/methods/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def import_metadata(
to_import: array of dicts, csv/xml string, `pandas.DataFrame`
Note:
If you pass a csv or xml string, you should use the
`format` parameter appropriately.
`import_format` parameter appropriately.
return_format_type:
Response format. By default, response will be json-decoded.
import_format:
Expand All @@ -150,7 +150,7 @@ def import_metadata(
to_import=to_import,
import_format=import_format,
return_format_type=return_format_type,
data_type="metadata",
content="metadata",
)

# pylint: disable=unsupported-assignment-operation
Expand Down
30 changes: 18 additions & 12 deletions redcap/methods/records.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,15 +165,18 @@ def export_records(
Examples:
>>> proj.export_records()
[{'record_id': '1', 'redcap_event_name': 'event_1_arm_1', 'field_1': '1',
[{'record_id': '1', 'redcap_event_name': 'event_1_arm_1', 'redcap_repeat_instrument': '',
'redcap_repeat_instance': 1, 'field_1': '1',
'checkbox_field___1': '0', 'checkbox_field___2': '1', 'upload_field': 'test_upload.txt',
'form_1_complete': '2'},
{'record_id': '2', 'redcap_event_name': 'event_1_arm_1', 'field_1': '0',
{'record_id': '2', 'redcap_event_name': 'event_1_arm_1', 'redcap_repeat_instrument': '',
'redcap_repeat_instance': 1, 'field_1': '0',
'checkbox_field___1': '0', 'checkbox_field___2': '0', 'upload_field': 'myupload.txt',
'form_1_complete': '0'}]
>>> proj.export_records(filter_logic="[field_1] = 1")
[{'record_id': '1', 'redcap_event_name': 'event_1_arm_1', 'field_1': '1',
[{'record_id': '1', 'redcap_event_name': 'event_1_arm_1', 'redcap_repeat_instrument': '',
'redcap_repeat_instance': 1, 'field_1': '1',
'checkbox_field___1': '0', 'checkbox_field___2': '1', 'upload_field': 'test_upload.txt',
'form_1_complete': '2'}]
Expand All @@ -187,10 +190,10 @@ def export_records(
>>> import pandas as pd
>>> pd.set_option("display.max_columns", 3)
>>> proj.export_records(format_type="df")
field_1 ... form_1_complete
record_id redcap_event_name ...
1 event_1_arm_1 1 ... 2
2 event_1_arm_1 0 ... 0
redcap_repeat_instrument ... form_1_complete
record_id redcap_event_name ...
1 event_1_arm_1 NaN ... 2
2 event_1_arm_1 NaN ... 0
...
"""
# pylint: enable=line-too-long
Expand Down Expand Up @@ -322,7 +325,7 @@ def import_records(
to_import:
Note:
If you pass a df, csv, or xml string, you should use the
`format` parameter appropriately.
`import_format` parameter appropriately.
Note:
Keys of the dictionaries should be subset of project's,
fields, but this isn't a requirement. If you provide keys
Expand Down Expand Up @@ -359,15 +362,15 @@ def import_records(
Union[Dict, str]: response from REDCap API, json-decoded if `return_format` == `'json'`
Examples:
>>> new_record = [{"record_id": 3, "field_1": 1}]
>>> new_record = [{"record_id": 3, "redcap_repeat_instance": 1, "field_1": 1}]
>>> proj.import_records(new_record)
{'count': 1}
"""
payload = self._initialize_import_payload(
to_import=to_import,
import_format=import_format,
return_format_type=return_format_type,
data_type="record",
content="record",
)

# pylint: disable=unsupported-assignment-operation
Expand Down Expand Up @@ -413,8 +416,11 @@ def delete_records(
Union[int, str]: Number of records deleted
Examples:
>>> new_record = [{"record_id": 3, "field_1": 1}, {"record_id": 4}]
>>> proj.import_records(new_record)
>>> new_records = [
... {"record_id": 3, "redcap_repeat_instance": 1, "field_1": 1},
... {"record_id": 4, "redcap_repeat_instance": 1}
... ]
>>> proj.import_records(new_records)
{'count': 2}
>>> proj.delete_records(["3", "4"])
2
Expand Down
92 changes: 92 additions & 0 deletions redcap/methods/repeating.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""REDCap API methods for Project repeating instruments"""
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, Literal

from redcap.methods.base import Base

if TYPE_CHECKING:
import pandas as pd


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

def export_repeating_instruments_events(
self,
format_type: Literal["json", "csv", "xml", "df"] = "json",
df_kwargs: Optional[Dict[str, Any]] = None,
):
"""
Export the project's repeating instruments and events settings
Args:
format_type:
Return the repeating instruments and events in native objects,
csv or xml, `'df''` will return a `pandas.DataFrame`
df_kwargs:
Passed to pandas.read_csv to control construction of
returned DataFrame
Returns:
Union[str, List[Dict[str, Any]], pd.DataFrame]: Repeating instruments and events
for the project
Examples:
>>> proj.export_repeating_instruments_events()
[{'event_name': 'event_1_arm_1', 'form_name': '', 'custom_form_label': ''}]
"""
payload = self._initialize_payload(
content="repeatingFormsEvents", 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="repeatingFormsEvents",
format_type=format_type,
df_kwargs=df_kwargs,
)

def import_repeating_instruments_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",
):
"""
Import repeating instrument and event settings 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]: The number of repeated instruments activated
Examples:
>>> rep_instruments = proj.export_repeating_instruments_events(format_type="csv")
>>> proj.import_repeating_instruments_events(rep_instruments, import_format="csv")
1
"""
payload = self._initialize_import_payload(
to_import=to_import,
import_format=import_format,
return_format_type=return_format_type,
content="repeatingFormsEvents",
)

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

return response
1 change: 1 addition & 0 deletions redcap/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class Project(
methods.metadata.Metadata,
methods.project_info.ProjectInfo,
methods.records.Records,
methods.repeating.Repeating,
methods.reports.Reports,
methods.surveys.Surveys,
methods.users.Users,
Expand Down
Loading

0 comments on commit 171411c

Please sign in to comment.