holoscan-cli 2.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- holoscan_cli/__init__.py +35 -0
- holoscan_cli/__main__.py +164 -0
- holoscan_cli/common/argparse_types.py +156 -0
- holoscan_cli/common/artifact_sources.py +160 -0
- holoscan_cli/common/constants.py +119 -0
- holoscan_cli/common/dockerutils.py +521 -0
- holoscan_cli/common/enum_types.py +49 -0
- holoscan_cli/common/exceptions.py +126 -0
- holoscan_cli/common/sdk_utils.py +195 -0
- holoscan_cli/common/utils.py +137 -0
- holoscan_cli/logging.json +37 -0
- holoscan_cli/nics/__init__.py +15 -0
- holoscan_cli/nics/nics.py +33 -0
- holoscan_cli/package-source.json +32 -0
- holoscan_cli/packager/__init__.py +15 -0
- holoscan_cli/packager/arguments.py +148 -0
- holoscan_cli/packager/config_reader.py +180 -0
- holoscan_cli/packager/container_builder.py +426 -0
- holoscan_cli/packager/manifest_files.py +217 -0
- holoscan_cli/packager/models.py +90 -0
- holoscan_cli/packager/package_command.py +197 -0
- holoscan_cli/packager/packager.py +124 -0
- holoscan_cli/packager/parameters.py +603 -0
- holoscan_cli/packager/platforms.py +426 -0
- holoscan_cli/packager/templates/Dockerfile.jinja2 +479 -0
- holoscan_cli/packager/templates/dockerignore +92 -0
- holoscan_cli/packager/templates/tools.sh +414 -0
- holoscan_cli/py.typed +14 -0
- holoscan_cli/runner/__init__.py +15 -0
- holoscan_cli/runner/resources.py +185 -0
- holoscan_cli/runner/run_command.py +207 -0
- holoscan_cli/runner/runner.py +340 -0
- holoscan_cli/version/__init__.py +15 -0
- holoscan_cli/version/version.py +53 -0
- holoscan_cli-2.9.0.dist-info/LICENSE +201 -0
- holoscan_cli-2.9.0.dist-info/METADATA +102 -0
- holoscan_cli-2.9.0.dist-info/RECORD +39 -0
- holoscan_cli-2.9.0.dist-info/WHEEL +4 -0
- holoscan_cli-2.9.0.dist-info/entry_points.txt +4 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
import logging
|
|
16
|
+
from argparse import Namespace
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Optional, Union
|
|
19
|
+
|
|
20
|
+
from ..common.artifact_sources import ArtifactSources
|
|
21
|
+
from ..common.constants import Constants
|
|
22
|
+
from ..common.dockerutils import image_exists
|
|
23
|
+
from ..common.enum_types import ApplicationType, SdkType
|
|
24
|
+
from ..common.exceptions import IncompatiblePlatformConfigurationError, InvalidSdkError
|
|
25
|
+
from ..common.sdk_utils import (
|
|
26
|
+
detect_sdk,
|
|
27
|
+
detect_sdk_version,
|
|
28
|
+
validate_holoscan_sdk_version,
|
|
29
|
+
)
|
|
30
|
+
from .parameters import PlatformParameters
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class Platform:
|
|
34
|
+
def __init__(self, artifact_sources: ArtifactSources) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Args:
|
|
37
|
+
args (Namespace): Input arguments for Packager from CLI
|
|
38
|
+
"""
|
|
39
|
+
self._logger = logging.getLogger("packager")
|
|
40
|
+
self._artifact_sources = artifact_sources
|
|
41
|
+
|
|
42
|
+
def configure_platforms(
|
|
43
|
+
self,
|
|
44
|
+
args: Namespace,
|
|
45
|
+
temp_dir: str,
|
|
46
|
+
version: str,
|
|
47
|
+
application_type: ApplicationType,
|
|
48
|
+
) -> tuple[SdkType, str, str, list[PlatformParameters]]:
|
|
49
|
+
"""Configures a list of platforms that need to be built.
|
|
50
|
+
1. Detect the SDK to use
|
|
51
|
+
2. Detect the version of the SDK to use
|
|
52
|
+
3. Ensure user provided arguments are valid and do not conflict
|
|
53
|
+
4. Builds a list of platforms to be built where each platform includes the following:
|
|
54
|
+
a. SDK distribution file(s) to use
|
|
55
|
+
b. Base image to use
|
|
56
|
+
c. Build image to use if application type is C++
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
args (Namespace): user provided arguments
|
|
60
|
+
temp_dir (str): temporary path for storing downloaded artifacts
|
|
61
|
+
version (str): application version
|
|
62
|
+
application_type (ApplicationType): application type
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Tuple[SdkType, str, str, List[PlatformParameters]]: A tuple contains SDK, Holoscan SDK
|
|
66
|
+
version, MONAI Deploy App SDK version and list of platforms to be built
|
|
67
|
+
"""
|
|
68
|
+
sdk = detect_sdk(args.sdk)
|
|
69
|
+
holoscan_sdk_version, monai_deploy_app_sdk_version = detect_sdk_version(
|
|
70
|
+
sdk, args.sdk_version
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
validate_holoscan_sdk_version(self._artifact_sources, holoscan_sdk_version)
|
|
74
|
+
|
|
75
|
+
self._validate_platform_options(args, sdk)
|
|
76
|
+
|
|
77
|
+
platforms = []
|
|
78
|
+
for platform in args.platform:
|
|
79
|
+
platform_config = args.platform_config
|
|
80
|
+
|
|
81
|
+
platform_parameters = PlatformParameters(
|
|
82
|
+
platform, platform_config, args.tag, version
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
(
|
|
86
|
+
platform_parameters.custom_base_image,
|
|
87
|
+
platform_parameters.base_image,
|
|
88
|
+
) = self._find_base_image(
|
|
89
|
+
platform_parameters, holoscan_sdk_version, args.base_image
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
platform_parameters.build_image = self._find_build_image(
|
|
93
|
+
platform_parameters,
|
|
94
|
+
holoscan_sdk_version,
|
|
95
|
+
application_type,
|
|
96
|
+
args.build_image,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
(
|
|
100
|
+
(
|
|
101
|
+
platform_parameters.custom_holoscan_sdk,
|
|
102
|
+
platform_parameters.holoscan_sdk_file,
|
|
103
|
+
),
|
|
104
|
+
(
|
|
105
|
+
platform_parameters.custom_monai_deploy_sdk,
|
|
106
|
+
platform_parameters.monai_deploy_sdk_file,
|
|
107
|
+
),
|
|
108
|
+
) = self._select_sdk_file(
|
|
109
|
+
platform_parameters,
|
|
110
|
+
temp_dir,
|
|
111
|
+
sdk,
|
|
112
|
+
holoscan_sdk_version,
|
|
113
|
+
monai_deploy_app_sdk_version,
|
|
114
|
+
application_type,
|
|
115
|
+
args.holoscan_sdk_file,
|
|
116
|
+
args.monai_deploy_sdk_file,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if sdk is SdkType.Holoscan:
|
|
120
|
+
platform_parameters.health_probe = self._artifact_sources.health_probe(
|
|
121
|
+
holoscan_sdk_version
|
|
122
|
+
)[platform_parameters.platform_arch.value]
|
|
123
|
+
|
|
124
|
+
platforms.append(platform_parameters)
|
|
125
|
+
|
|
126
|
+
return (sdk, holoscan_sdk_version, monai_deploy_app_sdk_version, platforms)
|
|
127
|
+
|
|
128
|
+
def _validate_platform_options(self, args: Namespace, sdk: SdkType):
|
|
129
|
+
"""Validates user requests.
|
|
130
|
+
- Packager accepts a single SDK distribution file, if user requests to build x64 and arm64
|
|
131
|
+
at the same time, raise exception
|
|
132
|
+
- If Packager was called using 'holoscan' command while user provides a MONAI Deploy APP
|
|
133
|
+
SDK file, raise exception
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
args (Namespace): user provided arguments
|
|
137
|
+
sdk (SdkType): SDK type
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
IncompatiblePlatformConfigurationError: when validation fails
|
|
141
|
+
"""
|
|
142
|
+
if (
|
|
143
|
+
args.holoscan_sdk_file is not None or args.monai_deploy_sdk_file is not None
|
|
144
|
+
) and len(args.platform) > 1:
|
|
145
|
+
raise IncompatiblePlatformConfigurationError(
|
|
146
|
+
"Validation error: '--sdk-file' cannot be used with multiple platforms."
|
|
147
|
+
)
|
|
148
|
+
if sdk == SdkType.Holoscan and args.monai_deploy_sdk_file is not None:
|
|
149
|
+
raise IncompatiblePlatformConfigurationError(
|
|
150
|
+
"--monai-deploy-sdk-file was used. Did you mean to use "
|
|
151
|
+
"'monai-deploy package' command instead?"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def _find_base_image(
|
|
155
|
+
self,
|
|
156
|
+
platform_parameters: PlatformParameters,
|
|
157
|
+
sdk_version: str,
|
|
158
|
+
base_image: Optional[str] = None,
|
|
159
|
+
) -> tuple[bool, str]:
|
|
160
|
+
"""
|
|
161
|
+
Ensure user provided base image exists in Docker or locate the base image to use based on
|
|
162
|
+
request platform.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
platform_parameters (PlatformParameters): target platform parameters
|
|
166
|
+
sdk_version (str): SDK version
|
|
167
|
+
base_image (Optional[str]): user provided base image
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
(Tuple(bool, str)): bool: True if using user provided image.
|
|
171
|
+
str: base image for building the image based on the given
|
|
172
|
+
platform and SDK version.
|
|
173
|
+
"""
|
|
174
|
+
if base_image is not None:
|
|
175
|
+
if image_exists(base_image):
|
|
176
|
+
return (True, base_image)
|
|
177
|
+
else:
|
|
178
|
+
raise InvalidSdkError(
|
|
179
|
+
f"Specified base image cannot be found: {base_image}"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
return (
|
|
184
|
+
False,
|
|
185
|
+
self._artifact_sources.base_image(sdk_version)[
|
|
186
|
+
platform_parameters.platform_config.value
|
|
187
|
+
],
|
|
188
|
+
)
|
|
189
|
+
except Exception as ex:
|
|
190
|
+
raise IncompatiblePlatformConfigurationError(
|
|
191
|
+
f"""No base image found for the selected configuration:
|
|
192
|
+
Platform: {platform_parameters.platform}
|
|
193
|
+
Configuration: {platform_parameters.platform_config}
|
|
194
|
+
Version: {sdk_version}"""
|
|
195
|
+
) from ex
|
|
196
|
+
|
|
197
|
+
def _find_build_image(
|
|
198
|
+
self,
|
|
199
|
+
platform_parameters: PlatformParameters,
|
|
200
|
+
sdk_version: str,
|
|
201
|
+
application_type: ApplicationType,
|
|
202
|
+
build_image: Optional[str] = None,
|
|
203
|
+
) -> Optional[str]:
|
|
204
|
+
"""
|
|
205
|
+
Ensure user provided build image exists or locate the build image to use based on the
|
|
206
|
+
requested platform.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
platform_parameters (PlatformParameters): target platform parameters
|
|
210
|
+
sdk_version (str): SDK version
|
|
211
|
+
application_type (ApplicationType): application type
|
|
212
|
+
build_image (Optional[str]): user provided build image
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
(str): build image for building the image based on the given platform and SDK version.
|
|
216
|
+
"""
|
|
217
|
+
if build_image is not None:
|
|
218
|
+
if image_exists(build_image):
|
|
219
|
+
return build_image
|
|
220
|
+
else:
|
|
221
|
+
raise InvalidSdkError(
|
|
222
|
+
f"Specified build image cannot be found: {build_image}"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
if application_type == ApplicationType.CppCMake:
|
|
226
|
+
try:
|
|
227
|
+
return self._artifact_sources.build_images(sdk_version)[
|
|
228
|
+
platform_parameters.platform_config.value
|
|
229
|
+
][platform_parameters.platform.value]
|
|
230
|
+
except Exception as ex:
|
|
231
|
+
raise IncompatiblePlatformConfigurationError(
|
|
232
|
+
f"No build image found for the selected configuration:"
|
|
233
|
+
f"\n Platform: {platform_parameters.platform.value}"
|
|
234
|
+
f"\n Configuration: {platform_parameters.platform_config.value}"
|
|
235
|
+
f"\n Version: {sdk_version}"
|
|
236
|
+
) from ex
|
|
237
|
+
else:
|
|
238
|
+
return None
|
|
239
|
+
|
|
240
|
+
def _select_sdk_file(
|
|
241
|
+
self,
|
|
242
|
+
platform_parameters: PlatformParameters,
|
|
243
|
+
temp_dir: str,
|
|
244
|
+
sdk: SdkType,
|
|
245
|
+
holoscan_sdk_version: str,
|
|
246
|
+
monai_deploy_app_sdk_version: Optional[str],
|
|
247
|
+
application_type: ApplicationType,
|
|
248
|
+
holoscan_sdk_file: Optional[Path] = None,
|
|
249
|
+
monai_deploy_sdk_file: Optional[Path] = None,
|
|
250
|
+
) -> tuple[
|
|
251
|
+
tuple[bool, Union[Path, str]],
|
|
252
|
+
tuple[
|
|
253
|
+
Union[Optional[Path], Optional[str]], Union[Optional[Path], Optional[str]]
|
|
254
|
+
],
|
|
255
|
+
]:
|
|
256
|
+
"""
|
|
257
|
+
Detects the SDK distributable to use based on internal mapping or user input.
|
|
258
|
+
|
|
259
|
+
- C++ & binary applications, attempt to download the SDK file.
|
|
260
|
+
- Python application, use Holoscan or MONAI Deploy App SDK PyPI package.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
platform_parameters (PlatformParameters): target platform parameters
|
|
264
|
+
temp_dir (str): temporary location for storing downloaded files.
|
|
265
|
+
sdk (SdkType): SDK to use
|
|
266
|
+
holoscan_sdk_version (str): Holoscan SDK version
|
|
267
|
+
monai_deploy_app_sdk_version (Optional[str]): MONAI Deploy SDK version
|
|
268
|
+
application_type (ApplicationType): application type
|
|
269
|
+
holoscan_sdk_file (Optional[Path]): path to the user specified Holoscan SDK file
|
|
270
|
+
monai_deploy_sdk_file (Optional[Path]): path to the user specified MONAI Deploy
|
|
271
|
+
App SDK file
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
(Tuple[Union[Path, str], Union[Path, str, None]]): A tuple where the first value
|
|
275
|
+
contains Holoscan SDK and the second value contains MONAI Deploy App SDK.
|
|
276
|
+
|
|
277
|
+
Raises:
|
|
278
|
+
InvalidSdkError: when user specified SDK file does not pass validation.
|
|
279
|
+
"""
|
|
280
|
+
if sdk == SdkType.Holoscan:
|
|
281
|
+
return (
|
|
282
|
+
self._get_holoscan_sdk(
|
|
283
|
+
platform_parameters,
|
|
284
|
+
temp_dir,
|
|
285
|
+
SdkType.Holoscan,
|
|
286
|
+
holoscan_sdk_version,
|
|
287
|
+
application_type,
|
|
288
|
+
holoscan_sdk_file,
|
|
289
|
+
),
|
|
290
|
+
(None, None),
|
|
291
|
+
)
|
|
292
|
+
elif sdk == SdkType.MonaiDeploy:
|
|
293
|
+
if monai_deploy_app_sdk_version is None:
|
|
294
|
+
raise InvalidSdkError("MONAI Deploy App SDK version missing")
|
|
295
|
+
return (
|
|
296
|
+
self._get_holoscan_sdk(
|
|
297
|
+
platform_parameters,
|
|
298
|
+
temp_dir,
|
|
299
|
+
SdkType.Holoscan,
|
|
300
|
+
holoscan_sdk_version,
|
|
301
|
+
application_type,
|
|
302
|
+
holoscan_sdk_file,
|
|
303
|
+
),
|
|
304
|
+
self._get_monai_deploy_sdk(
|
|
305
|
+
monai_deploy_app_sdk_version, monai_deploy_sdk_file
|
|
306
|
+
),
|
|
307
|
+
)
|
|
308
|
+
return (None, None)
|
|
309
|
+
|
|
310
|
+
def _get_holoscan_sdk(
|
|
311
|
+
self,
|
|
312
|
+
platform_parameters: PlatformParameters,
|
|
313
|
+
temp_dir: str,
|
|
314
|
+
sdk: SdkType,
|
|
315
|
+
sdk_version: str,
|
|
316
|
+
application_type: ApplicationType,
|
|
317
|
+
sdk_file: Optional[Path] = None,
|
|
318
|
+
) -> tuple[bool, Union[Path, str]]:
|
|
319
|
+
"""
|
|
320
|
+
Validates Holoscan SDK redistributable file if specified.
|
|
321
|
+
Otherwise, attempt to download the SDK file from internet.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
platform_parameters (PlatformParameters): Platform parameters
|
|
325
|
+
temp_dir (str): Temporary location for storing downloaded distribution files.
|
|
326
|
+
sdk (SdkType): SDK for building the application
|
|
327
|
+
sdk_version (str): SDK version. Defaults to None.
|
|
328
|
+
application_type (ApplicationType): application type
|
|
329
|
+
sdk_file (Optional[Path], optional): SDK file from user input. Defaults to None.
|
|
330
|
+
|
|
331
|
+
Raises:
|
|
332
|
+
InvalidSdkError: when an invalid SDK file is provided or unable to find matching SDK
|
|
333
|
+
file.
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
Tuple[bool, Union[Path, str]]:
|
|
337
|
+
bool: True when user provides SDk file. Otherwise, False.
|
|
338
|
+
Union[Path, str]: User provided SDK file path or package version.
|
|
339
|
+
"""
|
|
340
|
+
assert sdk is SdkType.Holoscan
|
|
341
|
+
|
|
342
|
+
if sdk_file is not None:
|
|
343
|
+
if application_type in [
|
|
344
|
+
ApplicationType.PythonModule,
|
|
345
|
+
ApplicationType.PythonFile,
|
|
346
|
+
]:
|
|
347
|
+
if sdk_file.suffix not in Constants.PYPI_FILE_EXTENSIONS:
|
|
348
|
+
raise InvalidSdkError(
|
|
349
|
+
"Invalid SDK file format, must be a PyPI wheel file with .whl file "
|
|
350
|
+
"extension."
|
|
351
|
+
)
|
|
352
|
+
return (True, sdk_file)
|
|
353
|
+
elif application_type in [
|
|
354
|
+
ApplicationType.CppCMake,
|
|
355
|
+
ApplicationType.Binary,
|
|
356
|
+
]:
|
|
357
|
+
if sdk_file.suffix != Constants.DEBIAN_FILE_EXTENSION:
|
|
358
|
+
raise InvalidSdkError(
|
|
359
|
+
"Invalid SDK file format, must be a Debian package file with .deb "
|
|
360
|
+
"file extension."
|
|
361
|
+
)
|
|
362
|
+
return (True, sdk_file)
|
|
363
|
+
|
|
364
|
+
raise InvalidSdkError(f"Unknown application type: {application_type.value}")
|
|
365
|
+
else:
|
|
366
|
+
if application_type in [
|
|
367
|
+
ApplicationType.PythonModule,
|
|
368
|
+
ApplicationType.PythonFile,
|
|
369
|
+
]:
|
|
370
|
+
wheel_package_version = self._artifact_sources.wheel_package_version(
|
|
371
|
+
sdk_version
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
if wheel_package_version is None:
|
|
375
|
+
raise InvalidSdkError(
|
|
376
|
+
"Unable to locate matching Holoscan SDK PyPI package with "
|
|
377
|
+
f"version {sdk_version}."
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
return (False, wheel_package_version)
|
|
381
|
+
elif application_type in [
|
|
382
|
+
ApplicationType.CppCMake,
|
|
383
|
+
ApplicationType.Binary,
|
|
384
|
+
]:
|
|
385
|
+
debian_package_version = self._artifact_sources.debian_package_version(
|
|
386
|
+
sdk_version
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
if debian_package_version is None:
|
|
390
|
+
raise InvalidSdkError(
|
|
391
|
+
"Unable to locate matching Holoscan SDK Debian package with "
|
|
392
|
+
f"version {sdk_version}."
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
return (False, debian_package_version)
|
|
396
|
+
|
|
397
|
+
raise InvalidSdkError(f"Unknown application type: {application_type.value}")
|
|
398
|
+
|
|
399
|
+
def _get_monai_deploy_sdk(
|
|
400
|
+
self,
|
|
401
|
+
monai_deploy_app_sdk_version: Optional[str],
|
|
402
|
+
sdk_file: Optional[Path] = None,
|
|
403
|
+
) -> tuple[bool, Union[Optional[Path], Optional[str]]]:
|
|
404
|
+
"""
|
|
405
|
+
Validates MONAI Deploy SDK redistributable file if specified.
|
|
406
|
+
Otherwise, Docker build stage will install the SDK from PyPI.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
sdk_file (Optional[Path], optional): SDK file from user input. Defaults to None.
|
|
410
|
+
monai_deploy_app_sdk_version: (Optional[str]): MONAI Deploy App SDK version
|
|
411
|
+
Raises:
|
|
412
|
+
InvalidSdkError: when an invalid SDK file is provided or unable to find matching SDK
|
|
413
|
+
file.
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
Union[Path, str]: Path to the SDK redistributable file.
|
|
417
|
+
"""
|
|
418
|
+
|
|
419
|
+
if sdk_file is not None:
|
|
420
|
+
if sdk_file.suffix not in Constants.PYPI_FILE_EXTENSIONS:
|
|
421
|
+
raise InvalidSdkError(
|
|
422
|
+
"Invalid SDK file format, must be a PyPI wheel file with .whl file extension."
|
|
423
|
+
)
|
|
424
|
+
return (True, sdk_file)
|
|
425
|
+
|
|
426
|
+
return (False, None)
|