Skip to content

Commit 190f918

Browse files
authored
Update SDK to support typeguard v3.0 and above, which has a breaking change in its API, and updated notebooks (#438)
* Change to support typeguard v3.0+ which has breaking API change on check_type() Along with the SDK change in th io_context.py, any pinged version of typarguard to ~=2 must be removed. Signed-off-by: M Q <mingmelvinq@nvidia.com> * Add comments on the reason for depending on pydicom and highdicom Signed-off-by: M Q <mingmelvinq@nvidia.com> * Add comments in the notebooks for the use of highdicom package Signed-off-by: M Q <mingmelvinq@nvidia.com> * Reran notebooks, aferwards, restored import "--upgrade" With "import --upgrade monai-deploy-app-sdk", users do not need to explicitly uninstall prior version of the App SDK when running the notebook in virtual env. For testing the notebook in dev with the editable App SDK, do NOT use --upgrade in case the local dirty version is lower somehow. Signed-off-by: M Q <mingmelvinq@nvidia.com> * Udpate pytest version to get the automated test in CI to succeed Signed-off-by: M Q <mingmelvinq@nvidia.com> * Bump to Python3.8 as in 3.7 pytest still fails on GitHub Signed-off-by: M Q <mingmelvinq@nvidia.com> * Fix flake8 complaint: B907, manually surrounded by quotes, consider using the `!r` conversion Signed-off-by: M Q <mingmelvinq@nvidia.com> * Quiet the format checking complaint Signed-off-by: M Q <mingmelvinq@nvidia.com> * To quiet flake8 complint Signed-off-by: M Q <mingmelvinq@nvidia.com> * Yet another black nit-picking Signed-off-by: M Q <mingmelvinq@nvidia.com> * Need to use the new way, pytest instead of py.test Signed-off-by: M Q <mingmelvinq@nvidia.com> * Somehow the last commit did not get the change py.test -> pytest Signed-off-by: M Q <mingmelvinq@nvidia.com> * Retested all notebooks as QA step Signed-off-by: M Q <mingmelvinq@nvidia.com> * Yet another way to quiet flake8 complaint Signed-off-by: M Q <mingmelvinq@nvidia.com> * The file content was wiped out (local disk space issue) Signed-off-by: M Q <mingmelvinq@nvidia.com> * Used the known working way to quiet flake8 complaint, str.format() Signed-off-by: M Q <mingmelvinq@nvidia.com> --------- Signed-off-by: M Q <mingmelvinq@nvidia.com>
1 parent fe9bb2b commit 190f918

30 files changed

+4493
-4702
lines changed

.readthedocs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ sphinx:
1818

1919
# Optionally set the version of Python and requirements required to build your docs
2020
python:
21-
version: 3.7
21+
version: "3.8"
2222
install:
2323
- requirements: docs/requirements.txt
2424
# system_packages: true

examples/apps/mednist_classifier_monaideploy/mednist_classifier_monaideploy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def compute(self, op_input: InputContext, op_output: OutputContext, context: Exe
9595

9696

9797
@md.resource(cpu=1, gpu=1, memory="1Gi")
98-
@md.env(pip_packages=["pydicom >= 2.3.0", "highdicom>=0.18.2", "typeguard~=2.12.1"])
98+
@md.env(pip_packages=["pydicom >= 2.3.0", "highdicom>=0.18.2"]) # because of the use of DICOM writer operator
9999
class App(Application):
100100
"""Application class for the MedNIST classifier."""
101101

monai/deploy/core/application.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -205,13 +205,13 @@ def add_flow(
205205
if not io_map:
206206
if len(op_output_labels) > 1:
207207
raise IOMappingError(
208-
f"The source operator has more than one output port "
208+
"The source operator has more than one output port "
209209
f"({', '.join(op_output_labels)}) so mapping should be specified explicitly!"
210210
)
211211
if len(op_input_labels) > 1:
212212
raise IOMappingError(
213213
f"The destination operator has more than one output port ({', '.join(op_input_labels)}) "
214-
f"so mapping should be specified explicitly!"
214+
"so mapping should be specified explicitly!"
215215
)
216216
io_map = {"": {""}}
217217

@@ -227,7 +227,7 @@ def add_flow(
227227
if len(op_output_labels) == 1 and len(output_labels) != 1:
228228
raise IOMappingError(
229229
f"The source operator({source_op.name}) has only one port with label "
230-
f"'{next(iter(op_output_labels))}' but io_map specifies {len(output_labels)} "
230+
f"{next(iter(op_output_labels))!r} but io_map specifies {len(output_labels)} "
231231
f"labels({', '.join(output_labels)}) to the source operator's output port"
232232
)
233233

@@ -239,7 +239,7 @@ def add_flow(
239239
del io_maps[output_label]
240240
break
241241
raise IOMappingError(
242-
f"The source operator({source_op.name}) has no output port with label '{output_label}'. "
242+
f"The source operator({source_op.name}) has no output port with label {output_label!r}. "
243243
f"It should be one of ({', '.join(op_output_labels)})."
244244
)
245245

@@ -250,7 +250,7 @@ def add_flow(
250250
if len(op_input_labels) == 1 and len(input_labels) != 1:
251251
raise IOMappingError(
252252
f"The destination operator({destination_op.name}) has only one port with label "
253-
f"'{next(iter(op_input_labels))}' but io_map specifies {len(input_labels)} "
253+
f"{next(iter(op_input_labels))!r} but io_map specifies {len(input_labels)} "
254254
f"labels({', '.join(input_labels)}) to the destination operator's input port"
255255
)
256256

@@ -262,8 +262,8 @@ def add_flow(
262262
input_labels.add(next(iter(op_input_labels)))
263263
break
264264
raise IOMappingError(
265-
f"The destination operator({destination_op.name}) has no input port with label '{input_label}'. "
266-
f"It should be one of ({', '.join(op_input_labels)})."
265+
f"The destination operator({destination_op.name}) has no input port with label {input_label!r}."
266+
f" It should be one of ({', '.join(op_input_labels)})."
267267
)
268268

269269
self._graph.add_flow(source_op, destination_op, io_maps)

monai/deploy/core/domain/datapath.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ def get(self, name: Optional[str] = "") -> DataPath:
6767
return next(iter(self._paths.values()))
6868
else:
6969
raise IOMappingError(
70-
f"'{name}' is not a valid name. It should be one of ({', '.join(self._paths.keys())})."
70+
f"{name!r} is not a valid name. It should be one of ({', '.join(self._paths.keys())})."
7171
)
7272
else:
7373
datapath = self._paths.get(name)
7474
if not datapath:
75-
raise ItemNotExistsError(f"A DataPath instance for '{name}' does not exist.")
75+
raise ItemNotExistsError(f"A DataPath instance for {name!r} does not exist.")
7676
return datapath

monai/deploy/core/env.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def __init__(self, pip_packages: Optional[Union[str, List[str]]] = None):
3737
if requirements_path.exists():
3838
pip_packages = requirements_path.read_text().strip().splitlines() # make it a list
3939
else:
40-
raise FileNotFoundError(f"The '{requirements_path}' file does not exist!")
40+
raise FileNotFoundError(f"The {requirements_path!r} file does not exist!")
4141

4242
self._pip_packages = list(pip_packages or [])
4343

monai/deploy/core/io_context.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def get_default_label(self, label: str = "") -> str:
4949
label = next(iter(self._labels))
5050
else:
5151
raise IOMappingError(
52-
f"'{label}' is not a valid {self._io_kind} of the operator({self._op.name}). "
52+
f"{label!r} is not a valid {self._io_kind} of the operator({self._op.name}). "
5353
f"It should be one of ({', '.join(self._labels)})."
5454
)
5555
return label
@@ -82,7 +82,7 @@ def get(self, label: str = "") -> Any:
8282
key = self.get_group_path(f"{self._io_kind}/{label}")
8383
storage = self._storage
8484
if not storage.exists(key):
85-
raise ItemNotExistsError(f"'{key}' does not exist.")
85+
raise ItemNotExistsError(f"{key!r} does not exist.")
8686
return storage.get(key)
8787

8888
def set(self, value: Any, label: str = ""):
@@ -109,10 +109,10 @@ def set(self, value: Any, label: str = ""):
109109
# checking: https://www.python.org/dev/peps/pep-0585/#id15
110110
data_type = self._op_info.get_data_type(self._io_kind, label)
111111
try:
112-
check_type("value", value, data_type)
112+
check_type(value, data_type)
113113
except TypeError as err:
114114
raise IOMappingError(
115-
f"The data type of '{label}' in the {self._io_kind} of '{self._op}' is {data_type}, but the value"
115+
f"The data type of {label!r} in the {self._io_kind} of {self._op!r} is {data_type}, but the value"
116116
f" to set is the data type of {type(value)}."
117117
) from err
118118

monai/deploy/core/models/model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ def get(self, name: str = "") -> "Model":
164164
if item:
165165
return item
166166
else:
167-
raise ItemNotExistsError(f"A model with '{name}' does not exist.")
167+
raise ItemNotExistsError(f"A model with {name!r} does not exist.")
168168
else:
169169
item_count = len(self._items)
170170
if item_count == 1:

monai/deploy/core/models/named_model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def accept(cls, path: str):
8383
# 3) Each model folder must contain only one model definition file or folder.
8484
if sum(1 for _ in model_folder.iterdir()) != 1:
8585
logger.warning(
86-
f"Model repository '{model_folder}' contains more than one model definition file or folder "
86+
f"Model repository {model_folder!r} contains more than one model definition file or folder "
8787
"so not treated as NamedModel."
8888
)
8989
return False, None

monai/deploy/core/resource.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,17 @@ def set_resource_limits(
6464
self._cpu = cpu_limit
6565
else:
6666
raise ItemAlreadyExistsError(
67-
f"'cpu' wouldn't be set to {cpu_limit} because it is already set to {self._cpu} by the runtime environment."
67+
f"'cpu' wouldn't be set to {cpu_limit} because it is already set to {self._cpu} by the runtime"
68+
" environment."
6869
)
6970

7071
if gpu_limit is not None:
7172
if self._gpu is None:
7273
self._gpu = gpu_limit
7374
else:
7475
raise ItemAlreadyExistsError(
75-
f"'gpu' wouldn't be set to {gpu_limit} because it is already set to {self._gpu} by the runtime environment."
76+
f"'gpu' wouldn't be set to {gpu_limit} because it is already set to {self._gpu} by the runtime"
77+
" environment."
7678
)
7779

7880
if type(memory_limit) == str:

monai/deploy/operators/dicom_data_loader_operator.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,8 @@ def test():
308308
print(f" 'StudyInstanceUID': {ds.StudyInstanceUID if ds.StudyInstanceUID else ''}")
309309
print(f" 'SeriesDescription': {ds.SeriesDescription if ds.SeriesDescription else ''}")
310310
print(
311-
f" 'IssuerOfPatientID': {ds.get('IssuerOfPatientID', '').repval if ds.get('IssuerOfPatientID', '') else '' }"
311+
" 'IssuerOfPatientID':"
312+
f" {ds.get('IssuerOfPatientID', '').repval if ds.get('IssuerOfPatientID', '') else '' }"
312313
)
313314
try:
314315
print(f" 'IssuerOfPatientID': {ds.IssuerOfPatientID if ds.IssuerOfPatientID else '' }")

monai/deploy/operators/dicom_series_selector_operator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def _select_all_series(self, dicom_study_list: List[DICOMStudy]) -> List[StudySe
177177
study_selected_series_list = []
178178
for study in dicom_study_list:
179179
logging.info(f"Working on study, instance UID: {study.StudyInstanceUID}")
180-
print((f"Working on study, instance UID: {study.StudyInstanceUID}"))
180+
print(f"Working on study, instance UID: {study.StudyInstanceUID}")
181181
study_selected_series = StudySelectedSeries(study)
182182
for series in study.get_all_series():
183183
logging.info(f"Working on series, instance UID: {str(series.SeriesInstanceUID)}")
@@ -213,7 +213,7 @@ def _select_series(self, attributes: dict, study: DICOMStudy, all_matched=False)
213213
matched = True
214214
# Simple matching on attribute value
215215
for key, value_to_match in attributes.items():
216-
logging.info(f"On attribute: '{key}' to match value: '{value_to_match}'")
216+
logging.info(f"On attribute: {key!r} to match value: {value_to_match!r}")
217217
# Ignore None
218218
if not value_to_match:
219219
continue

monai/deploy/operators/monai_bundle_inference_operator.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ def _read_from_archive(archive, root_name: str, config_name: str, do_search=True
8080
for suffix in bundle_suffixes:
8181
path = Path(root_name, config_folder, config_name).with_suffix(suffix)
8282
try:
83-
logging.debug(f"Trying to read config '{config_name}' content from {path}.")
83+
logging.debug(f"Trying to read config {config_name!r} content from {path}.")
8484
content_text = archive.read(str(path))
8585
break
8686
except Exception:
@@ -89,12 +89,12 @@ def _read_from_archive(archive, root_name: str, config_name: str, do_search=True
8989

9090
# Try search for the name in the name list of the archive
9191
if not content_text and do_search:
92-
logging.debug(f"Trying to find the file in the archive for config '{config_name}'.")
92+
logging.debug(f"Trying to find the file in the archive for config {config_name!r}.")
9393
name_list = archive.namelist()
9494
for suffix in bundle_suffixes:
9595
for n in name_list:
9696
if (f"{config_name}{suffix}").casefold in n.casefold():
97-
logging.debug(f"Trying to read content of config '{config_name}' from {n}.")
97+
logging.debug(f"Trying to read content of config {config_name!r} from {n}.")
9898
content_text = archive.read(n)
9999
break
100100

monai/deploy/packager/util.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,9 @@ def initialize_args(args: Namespace) -> Dict:
9393
dockerfile_type = verify_base_image(args.base)
9494
if not dockerfile_type:
9595
logger.error(
96-
"Provided base image '{}' is not supported \n \
97-
Please provide a ROCm or Cuda based Pytorch image from \n \
98-
https://hub.docker.com/r/rocm/pytorch or https://ngc.nvidia.com/ (nvcr.io/nvidia)".format(
99-
args.base
100-
)
96+
"Provided base image '{}' is not supported \n Please provide a ROCm or Cuda"
97+
" based Pytorch image from \n https://hub.docker.com/r/rocm/pytorch or"
98+
" https://ngc.nvidia.com/ (nvcr.io/nvidia)".format(args.base)
10199
)
102100

103101
sys.exit(1)
@@ -237,7 +235,7 @@ def build_image(args: dict, temp_dir: str):
237235
dockerignore_file.write(docker_ignore_template)
238236

239237
# Build dockerfile into an MAP image
240-
docker_build_cmd = f'''docker build -f "{docker_file_path}" -t {tag} "{temp_dir}"'''
238+
docker_build_cmd = f"""docker build -f {docker_file_path!r} -t {tag} {temp_dir!r}"""
241239
if sys.platform != "win32":
242240
docker_build_cmd += """ --build-arg MONAI_UID=$(id -u) --build-arg MONAI_GID=$(id -g)"""
243241
if no_cache:

monai/deploy/runner/runner.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def fetch_map_manifest(map_name: str) -> Tuple[dict, dict, int]:
4141

4242
with tempfile.TemporaryDirectory() as info_dir:
4343
if sys.platform == "win32":
44-
cmd = f'docker run --rm -a STDOUT -a STDERR -v "{info_dir}":/var/run/monai/export/config {map_name}'
44+
cmd = f'docker run --rm -a STDOUT -a STDERR -v " {info_dir}":/var/run/monai/export/config {map_name}'
4545
else:
4646
cmd = f"""docker_id=$(docker create {map_name})
4747
docker cp $docker_id:/etc/monai/app.json "{info_dir}/app.json"
@@ -103,8 +103,8 @@ def run_app(map_name: str, input_path: Path, output_path: Path, app_info: dict,
103103
if not posixpath.isabs(map_output):
104104
map_output = posixpath.join(app_info["working-directory"], map_output)
105105

106-
cmd += f' -e MONAI_INPUTPATH="{map_input}"'
107-
cmd += f' -e MONAI_OUTPUTPATH="{map_output}"'
106+
cmd += ' -e MONAI_INPUTPATH="{}"'.format(map_input)
107+
cmd += ' -e MONAI_OUTPUTPATH="{}"'.format(map_output)
108108
# TODO(bhatt-piyush): Handle model environment correctly (maybe solved by fixing 'monai-exec')
109109
cmd += " -e MONAI_MODELPATH=/opt/monai/models"
110110

monai/deploy/utils/argparse_types.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def valid_dir_path(path: str) -> Path:
3636
if dir_path.is_dir():
3737
return dir_path
3838
else:
39-
raise argparse.ArgumentTypeError(f"Expected directory path: '{dir_path}' is not a directory")
39+
raise argparse.ArgumentTypeError(f"Expected directory path: {dir_path!r} is not a directory")
4040

4141
# create directory
4242
dir_path.mkdir(parents=True)
@@ -58,7 +58,7 @@ def valid_existing_dir_path(path: str) -> Path:
5858
dir_path = Path(path).absolute()
5959
if dir_path.exists() and dir_path.is_dir():
6060
return dir_path
61-
raise argparse.ArgumentTypeError(f"No such directory: '{dir_path}'")
61+
raise argparse.ArgumentTypeError(f"No such directory: {dir_path!r}")
6262

6363

6464
def valid_existing_path(path: str) -> Path:
@@ -76,4 +76,4 @@ def valid_existing_path(path: str) -> Path:
7676
file_path = Path(path).absolute()
7777
if file_path.exists():
7878
return file_path
79-
raise argparse.ArgumentTypeError(f"No such file/folder: '{file_path}'")
79+
raise argparse.ArgumentTypeError(f"No such file/folder: {file_path!r}")

monai/deploy/utils/importutil.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ def optional_import(
238238
# preparing lazy error message
239239
msg = descriptor.format(actual_cmd)
240240
if version and tb is None: # a pure version issue
241-
msg += f" (requires '{module} {version}' by '{version_checker.__name__}')"
241+
msg += f" (requires '{module} {version}' by {version_checker.__name__!r})"
242242
if exception_str:
243243
msg += f" ({exception_str})"
244244

monai/deploy/utils/sizeutil.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def get_bytes(size: Union[str, int]) -> int:
6666

6767
m = re.match(r"^\s*(?P<size>(([1-9]\d+)|\d)(\.\d+)?)\s*(?P<unit>[a-z]{1,3})?\s*$", size, re.IGNORECASE)
6868
if not m:
69-
raise ValueError(f"Invalid size string ('{size}').")
69+
raise ValueError(f"Invalid size string ({size!r}).")
7070

7171
parsed_size = float(m.group("size"))
7272

@@ -77,7 +77,7 @@ def get_bytes(size: Union[str, int]) -> int:
7777
parsed_unit = "b" # default to bytes
7878

7979
if parsed_unit not in BYTES_UNIT:
80-
raise ValueError(f"Invalid unit ('{parsed_unit}').")
80+
raise ValueError(f"Invalid unit ({parsed_unit!r}).")
8181

8282
return int(parsed_size * BYTES_UNIT[parsed_unit])
8383

@@ -115,7 +115,7 @@ def convert_bytes(num_bytes: int, unit: str = "Mi") -> Union[str, int]:
115115

116116
unit_lowered = unit.lower()
117117
if unit_lowered not in BYTES_UNIT:
118-
raise ValueError(f"Invalid unit ('{unit}').")
118+
raise ValueError(f"Invalid unit ({unit!r}).")
119119

120120
converted = float(num_bytes) / BYTES_UNIT[unit_lowered] * 10
121121

monai/deploy/utils/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def get_sdk_semver():
123123
if SEMVER_REGEX.match(semver_str):
124124
return semver_str
125125
else:
126-
raise ValueError(f"Invalid semver string: '{semver_str}' (from '{version_str}')")
126+
raise ValueError(f"Invalid semver string: {semver_str!r} (from {version_str!r})")
127127
else:
128128
raise ValueError(f"Invalid version string: {version_str}")
129129

0 commit comments

Comments
 (0)