Skip to content

Commit

Permalink
✨ Add mypy support (redcap-tools#258)
Browse files Browse the repository at this point in the history
  • Loading branch information
pwildenhain authored Mar 28, 2023
1 parent c003da8 commit 23605e0
Show file tree
Hide file tree
Showing 25 changed files with 874 additions and 1,338 deletions.
8 changes: 8 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[mypy]
warn_unused_configs = True

[mypy-tests/unit/callback_utils.*]
ignore_errors = True

[mypy-semantic_version]
ignore_missing_imports = True
1,165 changes: 690 additions & 475 deletions poetry.lock

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,14 @@ requests = "^2.20"
semantic-version = "^2.8.5"
pandas = {version = "^1.3.4", optional = true}

[tool.poetry.dev-dependencies]
[tool.poetry.extras]
data_science = ["pandas"]

[tool.poetry.group.dev.dependencies]
pytest = "^6.2.5"
pytest-cov = "^3.0.0"
pytest-black = {version = "^0.3.12", allow-prereleases = true}
pytest-black = "^0.3.12"
pytest-mypy = "^0.10.3"
pytest-pylint = "^0.18.0"
responses = "^0.14.0"
pytest-mock = "^3.6.1"
Expand All @@ -47,9 +51,6 @@ mkdocstrings = "^0.17.0"
pytest-doctestplus = "^0.11.2"
Jinja2 = "~3.0.0"

[tool.poetry.extras]
data_science = ["pandas"]

[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
2 changes: 1 addition & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[pytest]
doctest_optionflags = NORMALIZE_WHITESPACE ELLIPSIS FAIL_FAST REPORT_NDIFF
addopts = -rsxX -l --tb=short --strict --pylint --black --cov=redcap --cov-report=xml
addopts = -rsxX -l --tb=short --strict --pylint --black --cov=redcap --cov-report=xml --mypy
markers =
integration: test connects to redcapdemo.vanderbilt.edu server
# Keep current format for future version of pytest
Expand Down
156 changes: 24 additions & 132 deletions redcap/methods/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
List,
Literal,
Optional,
cast,
overload,
Tuple,
TYPE_CHECKING,
Expand All @@ -23,7 +24,6 @@
RedcapError,
FileUpload,
Json,
EmptyJson,
)

if TYPE_CHECKING:
Expand Down Expand Up @@ -56,11 +56,11 @@ def __init__(
self._request_kwargs = request_kwargs

# attributes which require API calls
self._metadata = None
self._forms = None
self._field_names = None
self._def_field = None
self._is_longitudinal = None
self._metadata: Optional[Json] = None
self._forms: Optional[List[str]] = None
self._field_names: Optional[List[str]] = None
self._def_field: Optional[str] = None
self._is_longitudinal: Optional[bool] = None

@property
def url(self) -> str:
Expand All @@ -73,11 +73,11 @@ def token(self) -> str:
return self._token

@property
def metadata(self) -> List[Dict[str, Any]]:
def metadata(self) -> Json:
"""Project metadata in JSON format"""
if self._metadata is None:
payload = self._initialize_payload("metadata", format_type="json")
self._metadata = self._call_api(payload, return_type="json")
self._metadata = cast(Json, self._call_api(payload, return_type="json"))

return self._metadata

Expand Down Expand Up @@ -236,6 +236,8 @@ def _filter_metadata(self, key: str, field_name: str) -> str:

def _filter_metadata(self, key: str, field_name: Optional[str] = None):
"""Safely filter project metadata based off requested column and field_name"""
res: Union[list, str]

if field_name:
try:
res = str(
Expand Down Expand Up @@ -312,6 +314,8 @@ def _initialize_import_payload(
content=content, return_format_type=return_format_type
)
if import_format == "df":
to_import = cast("pd.DataFrame", to_import)

buf = StringIO()
has_named_index = to_import.index.name is not None
to_import.to_csv(buf, index=has_named_index)
Expand All @@ -322,83 +326,30 @@ def _initialize_import_payload(
payload["data"] = json.dumps(to_import, separators=(",", ":"))
else:
# don't do anything to csv/xml
to_import = cast("str", to_import)
payload["data"] = to_import

payload["format"] = import_format
return payload

@overload
def _return_data(
self,
response: Json,
content: Literal[
"exportFieldNames",
"formEventMapping",
"metadata",
"participantList",
"project",
"record",
"report",
"user",
],
format_type: Literal["json"],
df_kwargs: None,
record_type: Literal["flat", "eav"] = "flat",
) -> Json:
...

@overload
def _return_data(
self,
response: str,
content: Literal[
"exportFieldNames",
"formEventMapping",
"metadata",
"participantList",
"project",
"record",
"report",
"user",
],
format_type: Literal["csv", "xml"],
df_kwargs: None,
record_type: Literal["flat", "eav"] = "flat",
) -> str:
...

@overload
def _return_data(
self,
response: str,
content: Literal[
"exportFieldNames",
"formEventMapping",
"metadata",
"participantList",
"project",
"record",
"report",
"user",
],
format_type: Literal["df"],
df_kwargs: Optional[Dict[str, Any]],
record_type: Literal["flat", "eav"] = "flat",
) -> "pd.DataFrame":
...

def _return_data(
self,
response: Union[Json, str],
content: Literal[
"dag",
"exportFieldNames",
"formEventMapping",
"log",
"metadata",
"participantList",
"project",
"record",
"report",
"user",
"userDagMapping",
"userRole",
"userRoleMapping",
"repeatingFormsEvents",
],
format_type: Literal["json", "csv", "xml", "df"],
df_kwargs: Optional[Dict[str, Any]] = None,
Expand Down Expand Up @@ -445,83 +396,24 @@ def _return_data(
else:
df_kwargs = {}

response = cast(str, response)

buf = StringIO(response)
dataframe = self._read_csv(buf, **df_kwargs)
buf.close()

return dataframe

@overload
def _call_api(
self,
payload: Dict[str, Any],
return_type: Literal["file_map"],
file: None,
) -> FileMap:
...

@overload
def _call_api(
self,
payload: Dict[str, Any],
return_type: Literal["json"],
file: None = None,
) -> Json:
...

@overload
def _call_api(
self,
payload: Dict[str, Any],
return_type: Literal["empty_json"],
file: FileUpload,
) -> EmptyJson:
...

@overload
def _call_api(
self,
payload: Dict[str, Any],
return_type: Literal["count_dict"],
file: None = None,
) -> Dict[str, int]:
...

@overload
def _call_api(
self,
payload: Dict[str, Any],
return_type: Literal["ids_list"],
file: None = None,
) -> List[str]:
...

@overload
def _call_api(
self,
payload: Dict[str, Any],
return_type: Literal["int"],
file: None = None,
) -> int:
...

@overload
def _call_api(
self,
payload: Dict[str, Any],
return_type: Literal["str"],
file: None = None,
) -> str:
...

def _call_api(
self,
payload: Dict[str, Any],
return_type: Literal[
"file_map", "json", "empty_json", "count_dict", "ids_list", "str", "int"
],
file: Optional[FileUpload] = None,
) -> Union[FileMap, Json, Dict[str, int], List[dict], List[str], int, str]:
) -> Union[
FileMap, Json, Dict[str, int], List[dict], List[str], int, str, Literal["1"]
]:
"""Make a POST Requst to the REDCap API
Args:
Expand Down
Loading

0 comments on commit 23605e0

Please sign in to comment.