hiddenlayer-sdk 0.1.2__py3-none-any.whl → 1.1.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- hiddenlayer/sdk/constants.py +1 -0
- hiddenlayer/sdk/models.py +45 -8
- hiddenlayer/sdk/rest/__init__.py +80 -3
- hiddenlayer/sdk/rest/api/__init__.py +3 -0
- hiddenlayer/sdk/rest/api/aidr_predictive_api.py +1 -1
- hiddenlayer/sdk/rest/api/health_api.py +272 -0
- hiddenlayer/sdk/rest/api/model_scan_api.py +1 -1
- hiddenlayer/sdk/rest/api/model_supply_chain_api.py +2927 -0
- hiddenlayer/sdk/rest/api/readiness_api.py +272 -0
- hiddenlayer/sdk/rest/api/sensor_api.py +354 -37
- hiddenlayer/sdk/rest/api_client.py +1 -1
- hiddenlayer/sdk/rest/configuration.py +4 -4
- hiddenlayer/sdk/rest/exceptions.py +1 -1
- hiddenlayer/sdk/rest/models/__init__.py +77 -3
- hiddenlayer/sdk/rest/models/address.py +110 -0
- hiddenlayer/sdk/rest/models/artifact.py +155 -0
- hiddenlayer/sdk/rest/models/artifact_change.py +108 -0
- hiddenlayer/sdk/rest/models/artifact_content.py +101 -0
- hiddenlayer/sdk/rest/models/artifact_location.py +109 -0
- hiddenlayer/sdk/rest/models/attachment.py +129 -0
- hiddenlayer/sdk/rest/models/code_flow.py +113 -0
- hiddenlayer/sdk/rest/models/configuration_override.py +108 -0
- hiddenlayer/sdk/rest/models/conversion.py +114 -0
- hiddenlayer/sdk/rest/models/create_sensor_request.py +1 -1
- hiddenlayer/sdk/rest/models/detections.py +101 -0
- hiddenlayer/sdk/rest/models/edge.py +108 -0
- hiddenlayer/sdk/rest/models/edge_traversal.py +122 -0
- hiddenlayer/sdk/rest/models/exception.py +113 -0
- hiddenlayer/sdk/rest/models/external_properties.py +273 -0
- hiddenlayer/sdk/rest/models/external_property_file_reference.py +102 -0
- hiddenlayer/sdk/rest/models/external_property_file_references.py +240 -0
- hiddenlayer/sdk/rest/models/file_details_v3.py +140 -0
- hiddenlayer/sdk/rest/models/file_scan_report_v3.py +132 -0
- hiddenlayer/sdk/rest/models/file_scan_reports_v3.py +95 -0
- hiddenlayer/sdk/rest/models/fix.py +113 -0
- hiddenlayer/sdk/rest/models/get_multipart_upload_response.py +1 -1
- hiddenlayer/sdk/rest/models/graph.py +123 -0
- hiddenlayer/sdk/rest/models/graph_traversal.py +97 -0
- hiddenlayer/sdk/rest/models/invocation.py +199 -0
- hiddenlayer/sdk/rest/models/location.py +146 -0
- hiddenlayer/sdk/rest/models/{validation_error_model_loc_inner.py → location_inner.py} +7 -7
- hiddenlayer/sdk/rest/models/location_relationship.py +107 -0
- hiddenlayer/sdk/rest/models/logical_location.py +104 -0
- hiddenlayer/sdk/rest/models/message.py +92 -0
- hiddenlayer/sdk/rest/models/mitre_atlas_inner.py +110 -0
- hiddenlayer/sdk/rest/models/model.py +1 -1
- hiddenlayer/sdk/rest/models/model_inventory_info.py +99 -0
- hiddenlayer/sdk/rest/models/model_query_response.py +1 -1
- hiddenlayer/sdk/rest/models/model_scan_api_v3_scan_model_version_id_patch200_response.py +87 -0
- hiddenlayer/sdk/rest/models/model_scan_api_v3_scan_query200_response.py +102 -0
- hiddenlayer/sdk/rest/models/multiformat_message_string.py +95 -0
- hiddenlayer/sdk/rest/models/multipart_upload_part.py +1 -1
- hiddenlayer/sdk/rest/models/node.py +122 -0
- hiddenlayer/sdk/rest/models/notification.py +157 -0
- hiddenlayer/sdk/rest/models/paged_response_with_total.py +94 -0
- hiddenlayer/sdk/rest/models/physical_location.py +94 -0
- hiddenlayer/sdk/rest/models/property_bag.py +101 -0
- hiddenlayer/sdk/rest/models/rectangle.py +110 -0
- hiddenlayer/sdk/rest/models/region.py +127 -0
- hiddenlayer/sdk/rest/models/replacement.py +103 -0
- hiddenlayer/sdk/rest/models/reporting_configuration.py +113 -0
- hiddenlayer/sdk/rest/models/reporting_descriptor.py +162 -0
- hiddenlayer/sdk/rest/models/reporting_descriptor_reference.py +103 -0
- hiddenlayer/sdk/rest/models/reporting_descriptor_relationship.py +115 -0
- hiddenlayer/sdk/rest/models/result.py +312 -0
- hiddenlayer/sdk/rest/models/result_provenance.py +133 -0
- hiddenlayer/sdk/rest/models/rule_details_inner.py +102 -0
- hiddenlayer/sdk/rest/models/run.py +318 -0
- hiddenlayer/sdk/rest/models/run_automation_details.py +129 -0
- hiddenlayer/sdk/rest/models/sarif210.py +123 -0
- hiddenlayer/sdk/rest/models/scan_create_request.py +87 -0
- hiddenlayer/sdk/rest/models/scan_detection_v3.py +156 -0
- hiddenlayer/sdk/rest/models/scan_header_v3.py +129 -0
- hiddenlayer/sdk/rest/models/scan_job.py +109 -0
- hiddenlayer/sdk/rest/models/scan_job_inventory.py +137 -0
- hiddenlayer/sdk/rest/models/scan_model_details_v3.py +95 -0
- hiddenlayer/sdk/rest/models/scan_model_ids_v3.py +89 -0
- hiddenlayer/sdk/rest/models/scan_model_request.py +1 -1
- hiddenlayer/sdk/rest/models/scan_report_v3.py +139 -0
- hiddenlayer/sdk/rest/models/{file_info.py → scan_results.py} +14 -6
- hiddenlayer/sdk/rest/models/scan_results_v2.py +30 -10
- hiddenlayer/sdk/rest/models/security_posture.py +89 -0
- hiddenlayer/sdk/rest/models/sensor_sor_model_card_query_response.py +101 -0
- hiddenlayer/sdk/rest/models/sensor_sor_model_card_response.py +127 -0
- hiddenlayer/sdk/rest/models/sensor_sor_query_filter.py +1 -1
- hiddenlayer/sdk/rest/models/sensor_sor_query_request.py +1 -1
- hiddenlayer/sdk/rest/models/special_locations.py +97 -0
- hiddenlayer/sdk/rest/models/stack.py +113 -0
- hiddenlayer/sdk/rest/models/stack_frame.py +104 -0
- hiddenlayer/sdk/rest/models/submission_response.py +1 -1
- hiddenlayer/sdk/rest/models/submission_v2.py +1 -1
- hiddenlayer/sdk/rest/models/suppression.py +133 -0
- hiddenlayer/sdk/rest/models/thread_flow.py +144 -0
- hiddenlayer/sdk/rest/models/thread_flow_location.py +166 -0
- hiddenlayer/sdk/rest/models/tool.py +107 -0
- hiddenlayer/sdk/rest/models/tool_component.py +251 -0
- hiddenlayer/sdk/rest/models/tool_component_reference.py +108 -0
- hiddenlayer/sdk/rest/models/translation_metadata.py +110 -0
- hiddenlayer/sdk/rest/models/validation_error_model.py +4 -4
- hiddenlayer/sdk/rest/models/version_control_details.py +108 -0
- hiddenlayer/sdk/rest/models/web_request.py +112 -0
- hiddenlayer/sdk/rest/models/web_response.py +112 -0
- hiddenlayer/sdk/rest/rest.py +1 -1
- hiddenlayer/sdk/services/model.py +51 -3
- hiddenlayer/sdk/services/model_scan.py +153 -105
- hiddenlayer/sdk/version.py +1 -1
- {hiddenlayer_sdk-0.1.2.dist-info → hiddenlayer_sdk-1.1.0.dist-info}/METADATA +40 -21
- hiddenlayer_sdk-1.1.0.dist-info/RECORD +118 -0
- {hiddenlayer_sdk-0.1.2.dist-info → hiddenlayer_sdk-1.1.0.dist-info}/WHEEL +1 -1
- hiddenlayer/sdk/enterprise/__init__.py +0 -0
- hiddenlayer/sdk/enterprise/enterprise_model_scan_api.py +0 -55
- hiddenlayer_sdk-0.1.2.dist-info/RECORD +0 -43
- {hiddenlayer_sdk-0.1.2.dist-info → hiddenlayer_sdk-1.1.0.dist-info}/LICENSE +0 -0
- {hiddenlayer_sdk-0.1.2.dist-info → hiddenlayer_sdk-1.1.0.dist-info}/top_level.txt +0 -0
@@ -1,19 +1,24 @@
|
|
1
|
+
import json
|
1
2
|
import os
|
2
3
|
import random
|
4
|
+
import tempfile
|
3
5
|
import time
|
4
6
|
import warnings
|
7
|
+
import zipfile
|
5
8
|
from datetime import datetime
|
6
9
|
from pathlib import Path
|
7
10
|
from typing import List, Optional, Union
|
8
11
|
from uuid import uuid4
|
9
12
|
|
13
|
+
from pydantic_core import ValidationError
|
14
|
+
|
10
15
|
from hiddenlayer.sdk.constants import ScanStatus
|
11
|
-
from hiddenlayer.sdk.
|
12
|
-
from hiddenlayer.sdk.
|
13
|
-
from hiddenlayer.sdk.rest.api import ModelScanApi, SensorApi
|
16
|
+
from hiddenlayer.sdk.models import EmptyScanResults, Sarif, ScanResults
|
17
|
+
from hiddenlayer.sdk.rest.api import ModelScanApi, ModelSupplyChainApi, SensorApi
|
14
18
|
from hiddenlayer.sdk.rest.api_client import ApiClient
|
15
19
|
from hiddenlayer.sdk.rest.models import MultipartUploadPart
|
16
20
|
from hiddenlayer.sdk.rest.models.model import Model
|
21
|
+
from hiddenlayer.sdk.rest.models.sarif210 import Sarif210
|
17
22
|
from hiddenlayer.sdk.services.model import ModelAPI
|
18
23
|
from hiddenlayer.sdk.utils import filter_path_objects, is_saas
|
19
24
|
|
@@ -31,24 +36,22 @@ EXCLUDE_FILE_TYPES = [
|
|
31
36
|
|
32
37
|
class ModelScanAPI:
|
33
38
|
def __init__(self, api_client: ApiClient) -> None:
|
34
|
-
self.is_saas = is_saas(api_client.configuration.host)
|
35
39
|
self._api_client = api_client
|
36
40
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
self._model_scan_api = EnterpriseModelScanApi(api_client=api_client)
|
41
|
+
self._model_supply_chain_api = ModelSupplyChainApi(api_client=api_client)
|
42
|
+
self._model_api = ModelAPI(api_client=api_client)
|
43
|
+
self._sensor_api = SensorApi(
|
44
|
+
api_client=api_client
|
45
|
+
) # lower level api of ModelAPI
|
46
|
+
|
47
|
+
self._model_scan_api = ModelScanApi(api_client=api_client)
|
45
48
|
|
46
49
|
def scan_file(
|
47
50
|
self,
|
48
51
|
*,
|
49
52
|
model_name: str,
|
50
53
|
model_path: Union[str, os.PathLike],
|
51
|
-
|
54
|
+
model_version: Optional[int] = None,
|
52
55
|
chunk_size: int = 16,
|
53
56
|
wait_for_results: bool = True,
|
54
57
|
) -> ScanResults:
|
@@ -57,67 +60,45 @@ class ModelScanAPI:
|
|
57
60
|
|
58
61
|
:param model_name: Name of the model to be shown on the HiddenLayer UI
|
59
62
|
:param model_path: Local path to the model file.
|
60
|
-
:param
|
63
|
+
:param model_version: Version of the model to be shown on the HiddenLayer UI.
|
61
64
|
:param chunk_size: Number of chunks of the file to upload at once, defaults to 4.
|
62
65
|
:param wait_for_results: True whether to wait for the scan to finish, defaults to True.
|
63
66
|
|
64
67
|
:returns: Scan Results
|
65
68
|
"""
|
66
69
|
|
67
|
-
warnings.warn(
|
68
|
-
"Use of the threads parameter is deprecated and will be removed in version 0.2.0.",
|
69
|
-
category=DeprecationWarning,
|
70
|
-
stacklevel=2,
|
71
|
-
)
|
72
|
-
|
73
70
|
file_path = Path(model_path)
|
74
71
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
data = f.read()
|
106
|
-
|
107
|
-
sensor = Model(
|
108
|
-
sensor_id=str(uuid4()),
|
109
|
-
created_at=datetime.now(),
|
110
|
-
tenant_id="0000",
|
111
|
-
plaintext_name=model_name,
|
112
|
-
active=True,
|
113
|
-
version=1,
|
114
|
-
)
|
115
|
-
|
116
|
-
self._model_scan_api: EnterpriseModelScanApi
|
117
|
-
self._model_scan_api.scan_model(sensor.sensor_id, data)
|
118
|
-
model_name = sensor.sensor_id
|
119
|
-
|
120
|
-
scan_results = self.get_scan_results(model_name=model_name)
|
72
|
+
filesize = file_path.stat().st_size
|
73
|
+
sensor = self._model_api.create_or_get(
|
74
|
+
model_name=model_name, model_version=model_version
|
75
|
+
)
|
76
|
+
upload = self._sensor_api.begin_multipart_upload(sensor.sensor_id, filesize)
|
77
|
+
|
78
|
+
with open(file_path, "rb") as f:
|
79
|
+
for i in range(0, len(upload.parts), chunk_size):
|
80
|
+
group: List[MultipartUploadPart] = upload.parts[i : i + chunk_size]
|
81
|
+
for part in group:
|
82
|
+
read_amount = part.end_offset - part.start_offset
|
83
|
+
f.seek(int(part.start_offset))
|
84
|
+
part_data = f.read(int(read_amount))
|
85
|
+
|
86
|
+
# The SaaS multipart upload returns a upload url for each part
|
87
|
+
# So there is no specified route
|
88
|
+
self._api_client.call_api(
|
89
|
+
"PUT",
|
90
|
+
part.upload_url,
|
91
|
+
body=part_data,
|
92
|
+
header_params={"Content-Type": "application/octet-binary"},
|
93
|
+
)
|
94
|
+
|
95
|
+
self._sensor_api.complete_multipart_upload(sensor.sensor_id, upload.upload_id)
|
96
|
+
|
97
|
+
self._model_scan_api.scan_model(sensor.sensor_id)
|
98
|
+
|
99
|
+
scan_results = self.get_scan_results(
|
100
|
+
model_name=model_name, model_version=model_version
|
101
|
+
)
|
121
102
|
|
122
103
|
base_delay = 0.1 # seconds
|
123
104
|
retries = 0
|
@@ -129,13 +110,13 @@ class ModelScanAPI:
|
|
129
110
|
0, 1
|
130
111
|
) # exponential back off retry
|
131
112
|
time.sleep(delay)
|
132
|
-
scan_results = self.get_scan_results(
|
113
|
+
scan_results = self.get_scan_results(
|
114
|
+
model_name=model_name, model_version=model_version
|
115
|
+
)
|
133
116
|
print(f"{file_path.name} scan status: {scan_results.status}")
|
134
117
|
|
135
|
-
scan_results = ScanResults.from_scanresultsv2(scan_results_v2=scan_results)
|
136
118
|
scan_results.file_name = file_path.name
|
137
119
|
scan_results.file_path = str(file_path)
|
138
|
-
scan_results.sensor_id = sensor.sensor_id
|
139
120
|
|
140
121
|
return scan_results
|
141
122
|
|
@@ -145,8 +126,8 @@ class ModelScanAPI:
|
|
145
126
|
model_name: str,
|
146
127
|
bucket: str,
|
147
128
|
key: str,
|
129
|
+
model_version: Optional[int] = None,
|
148
130
|
s3_client: Optional[object] = None,
|
149
|
-
threads: int = 1,
|
150
131
|
chunk_size: int = 4,
|
151
132
|
wait_for_results: bool = True,
|
152
133
|
) -> ScanResults:
|
@@ -156,9 +137,9 @@ class ModelScanAPI:
|
|
156
137
|
:param model_name: Name of the model to be shown on the HiddenLayer UI.
|
157
138
|
:param bucket: Name of the s3 bucket where the model file is stored.
|
158
139
|
:param key: Path to the model file on s3.
|
140
|
+
:param model_version: Version of the model to be shown on the HiddenLayer UI.
|
159
141
|
:param wait_for_results: True whether to wait for the scan to finish, defaults to True.
|
160
142
|
:param s3_client: boto3 s3 client.
|
161
|
-
:param threads: Number of threads used to upload the file, defaults to 1.
|
162
143
|
:param chunk_size: Number of chunks of the file to upload at once, defaults to 4.
|
163
144
|
:param wait_for_results: True whether to wait for the scan to finish, defaults to True.
|
164
145
|
|
@@ -174,7 +155,7 @@ class ModelScanAPI:
|
|
174
155
|
)
|
175
156
|
"""
|
176
157
|
try:
|
177
|
-
import boto3
|
158
|
+
import boto3 # type: ignore
|
178
159
|
except ImportError:
|
179
160
|
raise ImportError("Python package boto3 is not installed.")
|
180
161
|
|
@@ -184,14 +165,14 @@ class ModelScanAPI:
|
|
184
165
|
file_name = key.split("/")[-1]
|
185
166
|
|
186
167
|
try:
|
187
|
-
s3_client.download_file(bucket, key, f"/tmp/{file_name}")
|
168
|
+
s3_client.download_file(bucket, key, f"/tmp/{file_name}") # type: ignore
|
188
169
|
except Exception as e:
|
189
170
|
raise RuntimeError(f"Couldn't download model s3://{bucket}/{key}: {e}")
|
190
171
|
|
191
172
|
return self.scan_file(
|
192
173
|
model_path=f"/tmp/{file_name}",
|
193
174
|
model_name=model_name,
|
194
|
-
|
175
|
+
model_version=model_version,
|
195
176
|
chunk_size=chunk_size,
|
196
177
|
wait_for_results=wait_for_results,
|
197
178
|
)
|
@@ -203,9 +184,9 @@ class ModelScanAPI:
|
|
203
184
|
account_url: str,
|
204
185
|
container: str,
|
205
186
|
blob: str,
|
187
|
+
model_version: Optional[int] = None,
|
206
188
|
blob_service_client: Optional[object] = None,
|
207
189
|
credential: Optional[object] = None,
|
208
|
-
threads: int = 1,
|
209
190
|
chunk_size: int = 4,
|
210
191
|
wait_for_results: bool = True,
|
211
192
|
) -> ScanResults:
|
@@ -216,10 +197,10 @@ class ModelScanAPI:
|
|
216
197
|
:param account_url: Azure Blob url of where the file is stored.
|
217
198
|
:param container: Azure Blob container containing the model file.
|
218
199
|
:param blob: Path to the model file inside the Azure blob container.
|
200
|
+
:param model_version: Version of the model to be shown on the HiddenLayer UI.
|
219
201
|
:param blob_service_client: BlobServiceClient object. Defaults to creating one using DefaultCredential().
|
220
202
|
:param credential: Credential to be passed to the BlobServiceClient object, can be a credential object, SAS key, etc.
|
221
203
|
Defaults to `DefaultCredential`
|
222
|
-
:param threads: Number of threads used to upload the file, defaults to 1.
|
223
204
|
:param chunk_size: Number of chunks of the file to upload at once, defaults to 4.
|
224
205
|
:param wait_for_results: True whether to wait for the scan to finish, defaults to True.
|
225
206
|
|
@@ -250,11 +231,11 @@ class ModelScanAPI:
|
|
250
231
|
credential = DefaultAzureCredential()
|
251
232
|
|
252
233
|
if not blob_service_client:
|
253
|
-
blob_service_client = BlobServiceClient(account_url, credential=credential)
|
234
|
+
blob_service_client = BlobServiceClient(account_url, credential=credential) # type: ignore
|
254
235
|
|
255
236
|
file_name = blob.split("/")[-1]
|
256
237
|
|
257
|
-
blob_client = blob_service_client.get_blob_client(
|
238
|
+
blob_client = blob_service_client.get_blob_client( # type: ignore
|
258
239
|
container=container, blob=blob
|
259
240
|
)
|
260
241
|
|
@@ -271,7 +252,7 @@ class ModelScanAPI:
|
|
271
252
|
return self.scan_file(
|
272
253
|
model_path=f"/tmp/{file_name}",
|
273
254
|
model_name=model_name,
|
274
|
-
|
255
|
+
model_version=model_version,
|
275
256
|
chunk_size=chunk_size,
|
276
257
|
wait_for_results=wait_for_results,
|
277
258
|
)
|
@@ -289,15 +270,15 @@ class ModelScanAPI:
|
|
289
270
|
force_download: bool = False,
|
290
271
|
hf_token: Optional[Union[str, bool]] = None,
|
291
272
|
# HL parameters
|
292
|
-
threads: int = 1,
|
293
273
|
chunk_size: int = 4,
|
294
274
|
wait_for_results: bool = True,
|
295
|
-
) ->
|
275
|
+
) -> ScanResults:
|
296
276
|
"""
|
297
277
|
Scans a model on HuggingFace.
|
298
278
|
|
299
279
|
Note: Requires the `huggingface_hub` pip package to be installed.
|
300
280
|
|
281
|
+
:param repo_id: The HuggingFace repository id.
|
301
282
|
:param revision: An optional Git revision id which can be a branch name, a tag, or a commit hash.
|
302
283
|
:param local_dir: If provided, the downloaded files will be placed under this directory.
|
303
284
|
:param allow_file_patterns: If provided, only files matching at least one pattern are scanned.
|
@@ -306,7 +287,6 @@ class ModelScanAPI:
|
|
306
287
|
:param hf_token: A token to be used for the download.
|
307
288
|
If True, the token is read from the HuggingFace config folder.
|
308
289
|
If a string, it’s used as the authentication token.
|
309
|
-
:param threads: Number of threads used to upload the file, defaults to 1.
|
310
290
|
:param chunk_size: Number of chunks of the file to upload at once, defaults to 4.
|
311
291
|
:param wait_for_results: True whether to wait for the scan to finish, defaults to True.
|
312
292
|
|
@@ -337,52 +317,117 @@ class ModelScanAPI:
|
|
337
317
|
)
|
338
318
|
|
339
319
|
return self.scan_folder(
|
320
|
+
model_name=repo_id,
|
340
321
|
path=local_dir,
|
341
322
|
allow_file_patterns=allow_file_patterns,
|
342
323
|
ignore_file_patterns=ignore_file_patterns,
|
343
|
-
threads=threads,
|
344
324
|
chunk_size=chunk_size,
|
345
325
|
wait_for_results=wait_for_results,
|
346
326
|
)
|
347
327
|
|
348
|
-
def get_scan_results(
|
328
|
+
def get_scan_results(
|
329
|
+
self,
|
330
|
+
*,
|
331
|
+
model_name: str,
|
332
|
+
model_version: Optional[int] = None,
|
333
|
+
) -> ScanResults:
|
349
334
|
"""
|
350
335
|
Get results from a model scan.
|
351
336
|
|
352
337
|
:param model_name: Name of the model.
|
338
|
+
:param model_version: Version of the model. When the model version is not specified, the scan results for the latest version will be returned.
|
353
339
|
|
354
340
|
:returns: Scan results.
|
355
341
|
"""
|
356
342
|
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
sensor_id = model_name
|
343
|
+
response = self._sensor_api.sensor_sor_api_v3_model_cards_query_get(
|
344
|
+
model_name_eq=model_name, limit=1
|
345
|
+
)
|
346
|
+
model_id = response.results[0].model_id
|
362
347
|
|
363
|
-
|
348
|
+
scans = self._model_supply_chain_api.model_scan_api_v3_scan_query(
|
349
|
+
model_ids=[model_id], latest_per_model_version_only=True
|
350
|
+
)
|
351
|
+
if scans.total == 0:
|
352
|
+
return EmptyScanResults()
|
353
|
+
|
354
|
+
if scans.items is None:
|
355
|
+
return EmptyScanResults()
|
356
|
+
|
357
|
+
scan = scans.items[0]
|
358
|
+
if model_version:
|
359
|
+
scan = next(
|
360
|
+
(
|
361
|
+
s
|
362
|
+
for s in scans.items
|
363
|
+
if s.inventory.model_version == str(model_version)
|
364
|
+
),
|
365
|
+
None,
|
366
|
+
)
|
367
|
+
if not scan:
|
368
|
+
return EmptyScanResults()
|
369
|
+
|
370
|
+
scan_report = (
|
371
|
+
self._model_supply_chain_api.model_scan_api_v3_scan_model_version_id_get(
|
372
|
+
scan.scan_id
|
373
|
+
)
|
374
|
+
)
|
364
375
|
|
365
|
-
return ScanResults.
|
366
|
-
|
376
|
+
return ScanResults.from_scanreportv3(
|
377
|
+
scan_report_v3=scan_report, model_id=model_id
|
367
378
|
)
|
368
379
|
|
380
|
+
def get_sarif_results(
|
381
|
+
self,
|
382
|
+
*,
|
383
|
+
model_name: str,
|
384
|
+
model_version: Optional[int] = None,
|
385
|
+
) -> Optional[Sarif]:
|
386
|
+
"""
|
387
|
+
Get sarif results from a model scan.
|
388
|
+
|
389
|
+
:param model_name: Name of the model.
|
390
|
+
:param model_version: Version of the model. When the model version is not specified, the scan results for the latest version will be returned.
|
391
|
+
|
392
|
+
:returns: Scan results.
|
393
|
+
"""
|
394
|
+
scan = self.get_scan_results(model_name=model_name, model_version=model_version)
|
395
|
+
if scan.scan_id == "":
|
396
|
+
return None
|
397
|
+
|
398
|
+
# Unfortunately, the generated code for the API doesn't directly support modifying the Accept header
|
399
|
+
# in order to enable us to get the Sarif results
|
400
|
+
# Here we will reach in to the request serialization process. The 2nd element in the tuple is the headers
|
401
|
+
# where we will modify the Accept header to application/sarif+json
|
402
|
+
request = self._model_supply_chain_api._model_scan_api_v3_scan_model_version_id_get_serialize(
|
403
|
+
scan.scan_id, None, None, None, None, 0
|
404
|
+
)
|
405
|
+
request[2]["Accept"] = "application/sarif+json"
|
406
|
+
response = self._api_client.call_api(*request)
|
407
|
+
response.read()
|
408
|
+
return self._api_client.response_deserialize(
|
409
|
+
response_data=response, response_types_map={"200": Sarif}
|
410
|
+
).data # type: ignore
|
411
|
+
|
369
412
|
def scan_folder(
|
370
413
|
self,
|
371
414
|
*,
|
415
|
+
model_name: str,
|
372
416
|
path: Union[str, os.PathLike],
|
417
|
+
model_version: Optional[int] = None,
|
373
418
|
allow_file_patterns: Optional[List[str]] = None,
|
374
419
|
ignore_file_patterns: Optional[List[str]] = None,
|
375
|
-
threads: int = 1,
|
376
420
|
chunk_size: int = 4,
|
377
421
|
wait_for_results: bool = True,
|
378
|
-
) ->
|
422
|
+
) -> ScanResults:
|
379
423
|
"""
|
380
424
|
Submits all files in a directory and its sub directories to be scanned.
|
381
425
|
|
426
|
+
:param model_name: Name of the model to be shown on the HiddenLayer UI.
|
382
427
|
:param path: Path to the folder on disk to be scanned.
|
428
|
+
:param model_version: Version of the model to be shown on the HiddenLayer UI.
|
383
429
|
:param allow_file_patterns: If provided, only files matching at least one pattern are scanned.
|
384
430
|
:param ignore_file_patterns: If provided, files matching any of the patterns are not scanned.
|
385
|
-
:param threads: Number of threads used to upload the file, defaults to 1.
|
386
431
|
:param chunk_size: Number of chunks of the file to upload at once, defaults to 4.
|
387
432
|
:param wait_for_results: True whether to wait for the scan to finish, defaults to True.
|
388
433
|
|
@@ -390,6 +435,8 @@ class ModelScanAPI:
|
|
390
435
|
"""
|
391
436
|
|
392
437
|
model_path = Path(path)
|
438
|
+
filename = tempfile.NamedTemporaryFile().name + ".zip"
|
439
|
+
|
393
440
|
ignore_file_patterns = (
|
394
441
|
EXCLUDE_FILE_TYPES + ignore_file_patterns
|
395
442
|
if ignore_file_patterns
|
@@ -402,13 +449,14 @@ class ModelScanAPI:
|
|
402
449
|
ignore_patterns=ignore_file_patterns,
|
403
450
|
)
|
404
451
|
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
452
|
+
with zipfile.ZipFile(filename, "a") as zipf:
|
453
|
+
for file in files:
|
454
|
+
zipf.write(file, os.path.relpath(file, model_path))
|
455
|
+
|
456
|
+
return self.scan_file(
|
457
|
+
model_name=model_name,
|
458
|
+
model_version=model_version,
|
459
|
+
model_path=filename,
|
460
|
+
chunk_size=chunk_size,
|
461
|
+
wait_for_results=wait_for_results,
|
462
|
+
)
|
hiddenlayer/sdk/version.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
VERSION = "
|
1
|
+
VERSION = "1.1.0"
|
@@ -1,10 +1,10 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
2
2
|
Name: hiddenlayer-sdk
|
3
|
-
Version:
|
3
|
+
Version: 1.1.0
|
4
4
|
Summary: Official HiddenLayer Python SDK
|
5
5
|
Author-email: HiddenLayer Integrations Team <integrations@hiddenlayer.com>
|
6
6
|
Maintainer-email: HiddenLayer Integrations Team <integrations@hiddenlayer.com>
|
7
|
-
License:
|
7
|
+
License: Apache License
|
8
8
|
Version 2.0, January 2004
|
9
9
|
http://www.apache.org/licenses/
|
10
10
|
|
@@ -222,20 +222,20 @@ Requires-Python: >=3.8
|
|
222
222
|
Description-Content-Type: text/markdown
|
223
223
|
License-File: LICENSE
|
224
224
|
Requires-Dist: requests
|
225
|
-
Requires-Dist: pydantic
|
226
|
-
Requires-Dist: python-dateutil
|
227
|
-
Requires-Dist: numpy
|
228
|
-
Provides-Extra: aws
|
229
|
-
Requires-Dist: boto3 >=1.0.0 ; extra == 'aws'
|
230
|
-
Provides-Extra: azure
|
231
|
-
Requires-Dist: azure-storage-blob ; extra == 'azure'
|
232
|
-
Requires-Dist: azure-identity ; extra == 'azure'
|
225
|
+
Requires-Dist: pydantic>=1.10.9
|
226
|
+
Requires-Dist: python-dateutil>=2.0.0
|
227
|
+
Requires-Dist: numpy>=1.0.0
|
233
228
|
Provides-Extra: dev
|
234
|
-
Requires-Dist: ruff
|
235
|
-
Requires-Dist: pytest
|
236
|
-
Requires-Dist: pandas
|
229
|
+
Requires-Dist: ruff>=0.2.2; extra == "dev"
|
230
|
+
Requires-Dist: pytest; extra == "dev"
|
231
|
+
Requires-Dist: pandas; extra == "dev"
|
232
|
+
Provides-Extra: aws
|
233
|
+
Requires-Dist: boto3>=1.0.0; extra == "aws"
|
237
234
|
Provides-Extra: hf
|
238
|
-
Requires-Dist:
|
235
|
+
Requires-Dist: huggingface_hub; extra == "hf"
|
236
|
+
Provides-Extra: azure
|
237
|
+
Requires-Dist: azure-storage-blob; extra == "azure"
|
238
|
+
Requires-Dist: azure-identity; extra == "azure"
|
239
239
|
|
240
240
|
# HiddenLayer SDK Python (Beta)
|
241
241
|
|
@@ -277,25 +277,44 @@ hl_client = HiddenlayerServiceClient(
|
|
277
277
|
)
|
278
278
|
```
|
279
279
|
|
280
|
-
|
280
|
+
### Scanning Models
|
281
281
|
|
282
282
|
```python
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
host="https://your.hiddenlayer.enterprise.url",
|
283
|
+
hl_client.model_scanner.scan_file(
|
284
|
+
model_name="name_of_the_model",
|
285
|
+
model_path="path/to/model/file.pkl"
|
287
286
|
)
|
288
287
|
```
|
289
288
|
|
290
|
-
|
289
|
+
If you would like to specify the version associated with the model, you can pass that in as well. Note that the model version must not already exist.
|
291
290
|
|
292
291
|
```python
|
293
292
|
hl_client.model_scanner.scan_file(
|
294
293
|
model_name="name_of_the_model",
|
294
|
+
model_version=1,
|
295
295
|
model_path="path/to/model/file.pkl"
|
296
296
|
)
|
297
297
|
```
|
298
298
|
|
299
|
+
### Scanning Folders
|
300
|
+
|
301
|
+
```python
|
302
|
+
hl_client.model_scanner.scan_folder(
|
303
|
+
model_name="name_of_the_model",
|
304
|
+
path="path/to/model/"
|
305
|
+
)
|
306
|
+
```
|
307
|
+
|
308
|
+
If you would like to specify the version associated with the model, you can pass that in as well. Note that the model version must not already exist.
|
309
|
+
|
310
|
+
```python
|
311
|
+
hl_client.model_scanner.scan_folder(
|
312
|
+
model_name="name_of_the_model",
|
313
|
+
model_version=1,
|
314
|
+
path="path/to/model/"
|
315
|
+
)
|
316
|
+
```
|
317
|
+
|
299
318
|
### Using AIDR for Predictive Models
|
300
319
|
|
301
320
|
> Note: This is only supported using the SaaS version of the platform.
|