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.
Files changed (114) hide show
  1. hiddenlayer/sdk/constants.py +1 -0
  2. hiddenlayer/sdk/models.py +45 -8
  3. hiddenlayer/sdk/rest/__init__.py +80 -3
  4. hiddenlayer/sdk/rest/api/__init__.py +3 -0
  5. hiddenlayer/sdk/rest/api/aidr_predictive_api.py +1 -1
  6. hiddenlayer/sdk/rest/api/health_api.py +272 -0
  7. hiddenlayer/sdk/rest/api/model_scan_api.py +1 -1
  8. hiddenlayer/sdk/rest/api/model_supply_chain_api.py +2927 -0
  9. hiddenlayer/sdk/rest/api/readiness_api.py +272 -0
  10. hiddenlayer/sdk/rest/api/sensor_api.py +354 -37
  11. hiddenlayer/sdk/rest/api_client.py +1 -1
  12. hiddenlayer/sdk/rest/configuration.py +4 -4
  13. hiddenlayer/sdk/rest/exceptions.py +1 -1
  14. hiddenlayer/sdk/rest/models/__init__.py +77 -3
  15. hiddenlayer/sdk/rest/models/address.py +110 -0
  16. hiddenlayer/sdk/rest/models/artifact.py +155 -0
  17. hiddenlayer/sdk/rest/models/artifact_change.py +108 -0
  18. hiddenlayer/sdk/rest/models/artifact_content.py +101 -0
  19. hiddenlayer/sdk/rest/models/artifact_location.py +109 -0
  20. hiddenlayer/sdk/rest/models/attachment.py +129 -0
  21. hiddenlayer/sdk/rest/models/code_flow.py +113 -0
  22. hiddenlayer/sdk/rest/models/configuration_override.py +108 -0
  23. hiddenlayer/sdk/rest/models/conversion.py +114 -0
  24. hiddenlayer/sdk/rest/models/create_sensor_request.py +1 -1
  25. hiddenlayer/sdk/rest/models/detections.py +101 -0
  26. hiddenlayer/sdk/rest/models/edge.py +108 -0
  27. hiddenlayer/sdk/rest/models/edge_traversal.py +122 -0
  28. hiddenlayer/sdk/rest/models/exception.py +113 -0
  29. hiddenlayer/sdk/rest/models/external_properties.py +273 -0
  30. hiddenlayer/sdk/rest/models/external_property_file_reference.py +102 -0
  31. hiddenlayer/sdk/rest/models/external_property_file_references.py +240 -0
  32. hiddenlayer/sdk/rest/models/file_details_v3.py +140 -0
  33. hiddenlayer/sdk/rest/models/file_scan_report_v3.py +132 -0
  34. hiddenlayer/sdk/rest/models/file_scan_reports_v3.py +95 -0
  35. hiddenlayer/sdk/rest/models/fix.py +113 -0
  36. hiddenlayer/sdk/rest/models/get_multipart_upload_response.py +1 -1
  37. hiddenlayer/sdk/rest/models/graph.py +123 -0
  38. hiddenlayer/sdk/rest/models/graph_traversal.py +97 -0
  39. hiddenlayer/sdk/rest/models/invocation.py +199 -0
  40. hiddenlayer/sdk/rest/models/location.py +146 -0
  41. hiddenlayer/sdk/rest/models/{validation_error_model_loc_inner.py → location_inner.py} +7 -7
  42. hiddenlayer/sdk/rest/models/location_relationship.py +107 -0
  43. hiddenlayer/sdk/rest/models/logical_location.py +104 -0
  44. hiddenlayer/sdk/rest/models/message.py +92 -0
  45. hiddenlayer/sdk/rest/models/mitre_atlas_inner.py +110 -0
  46. hiddenlayer/sdk/rest/models/model.py +1 -1
  47. hiddenlayer/sdk/rest/models/model_inventory_info.py +99 -0
  48. hiddenlayer/sdk/rest/models/model_query_response.py +1 -1
  49. hiddenlayer/sdk/rest/models/model_scan_api_v3_scan_model_version_id_patch200_response.py +87 -0
  50. hiddenlayer/sdk/rest/models/model_scan_api_v3_scan_query200_response.py +102 -0
  51. hiddenlayer/sdk/rest/models/multiformat_message_string.py +95 -0
  52. hiddenlayer/sdk/rest/models/multipart_upload_part.py +1 -1
  53. hiddenlayer/sdk/rest/models/node.py +122 -0
  54. hiddenlayer/sdk/rest/models/notification.py +157 -0
  55. hiddenlayer/sdk/rest/models/paged_response_with_total.py +94 -0
  56. hiddenlayer/sdk/rest/models/physical_location.py +94 -0
  57. hiddenlayer/sdk/rest/models/property_bag.py +101 -0
  58. hiddenlayer/sdk/rest/models/rectangle.py +110 -0
  59. hiddenlayer/sdk/rest/models/region.py +127 -0
  60. hiddenlayer/sdk/rest/models/replacement.py +103 -0
  61. hiddenlayer/sdk/rest/models/reporting_configuration.py +113 -0
  62. hiddenlayer/sdk/rest/models/reporting_descriptor.py +162 -0
  63. hiddenlayer/sdk/rest/models/reporting_descriptor_reference.py +103 -0
  64. hiddenlayer/sdk/rest/models/reporting_descriptor_relationship.py +115 -0
  65. hiddenlayer/sdk/rest/models/result.py +312 -0
  66. hiddenlayer/sdk/rest/models/result_provenance.py +133 -0
  67. hiddenlayer/sdk/rest/models/rule_details_inner.py +102 -0
  68. hiddenlayer/sdk/rest/models/run.py +318 -0
  69. hiddenlayer/sdk/rest/models/run_automation_details.py +129 -0
  70. hiddenlayer/sdk/rest/models/sarif210.py +123 -0
  71. hiddenlayer/sdk/rest/models/scan_create_request.py +87 -0
  72. hiddenlayer/sdk/rest/models/scan_detection_v3.py +156 -0
  73. hiddenlayer/sdk/rest/models/scan_header_v3.py +129 -0
  74. hiddenlayer/sdk/rest/models/scan_job.py +109 -0
  75. hiddenlayer/sdk/rest/models/scan_job_inventory.py +137 -0
  76. hiddenlayer/sdk/rest/models/scan_model_details_v3.py +95 -0
  77. hiddenlayer/sdk/rest/models/scan_model_ids_v3.py +89 -0
  78. hiddenlayer/sdk/rest/models/scan_model_request.py +1 -1
  79. hiddenlayer/sdk/rest/models/scan_report_v3.py +139 -0
  80. hiddenlayer/sdk/rest/models/{file_info.py → scan_results.py} +14 -6
  81. hiddenlayer/sdk/rest/models/scan_results_v2.py +30 -10
  82. hiddenlayer/sdk/rest/models/security_posture.py +89 -0
  83. hiddenlayer/sdk/rest/models/sensor_sor_model_card_query_response.py +101 -0
  84. hiddenlayer/sdk/rest/models/sensor_sor_model_card_response.py +127 -0
  85. hiddenlayer/sdk/rest/models/sensor_sor_query_filter.py +1 -1
  86. hiddenlayer/sdk/rest/models/sensor_sor_query_request.py +1 -1
  87. hiddenlayer/sdk/rest/models/special_locations.py +97 -0
  88. hiddenlayer/sdk/rest/models/stack.py +113 -0
  89. hiddenlayer/sdk/rest/models/stack_frame.py +104 -0
  90. hiddenlayer/sdk/rest/models/submission_response.py +1 -1
  91. hiddenlayer/sdk/rest/models/submission_v2.py +1 -1
  92. hiddenlayer/sdk/rest/models/suppression.py +133 -0
  93. hiddenlayer/sdk/rest/models/thread_flow.py +144 -0
  94. hiddenlayer/sdk/rest/models/thread_flow_location.py +166 -0
  95. hiddenlayer/sdk/rest/models/tool.py +107 -0
  96. hiddenlayer/sdk/rest/models/tool_component.py +251 -0
  97. hiddenlayer/sdk/rest/models/tool_component_reference.py +108 -0
  98. hiddenlayer/sdk/rest/models/translation_metadata.py +110 -0
  99. hiddenlayer/sdk/rest/models/validation_error_model.py +4 -4
  100. hiddenlayer/sdk/rest/models/version_control_details.py +108 -0
  101. hiddenlayer/sdk/rest/models/web_request.py +112 -0
  102. hiddenlayer/sdk/rest/models/web_response.py +112 -0
  103. hiddenlayer/sdk/rest/rest.py +1 -1
  104. hiddenlayer/sdk/services/model.py +51 -3
  105. hiddenlayer/sdk/services/model_scan.py +153 -105
  106. hiddenlayer/sdk/version.py +1 -1
  107. {hiddenlayer_sdk-0.1.2.dist-info → hiddenlayer_sdk-1.1.0.dist-info}/METADATA +40 -21
  108. hiddenlayer_sdk-1.1.0.dist-info/RECORD +118 -0
  109. {hiddenlayer_sdk-0.1.2.dist-info → hiddenlayer_sdk-1.1.0.dist-info}/WHEEL +1 -1
  110. hiddenlayer/sdk/enterprise/__init__.py +0 -0
  111. hiddenlayer/sdk/enterprise/enterprise_model_scan_api.py +0 -55
  112. hiddenlayer_sdk-0.1.2.dist-info/RECORD +0 -43
  113. {hiddenlayer_sdk-0.1.2.dist-info → hiddenlayer_sdk-1.1.0.dist-info}/LICENSE +0 -0
  114. {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.enterprise.enterprise_model_scan_api import EnterpriseModelScanApi
12
- from hiddenlayer.sdk.models import ScanResults
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
- if self.is_saas:
38
- self._model_scan_api = ModelScanApi(api_client=api_client)
39
- self._model_api = ModelAPI(api_client=api_client)
40
- self._sensor_api = SensorApi(
41
- api_client=api_client
42
- ) # lower level api of ModelAPI
43
- else:
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
- threads: int = 1,
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 threads: Number of threads used to upload the file, defaults to 1.
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
- # Can combine the 2 paths when SaaS API and Enterprise APIs are in sync
76
- if self.is_saas:
77
- filesize = file_path.stat().st_size
78
- sensor = self._model_api.create(model_name=model_name)
79
- upload = self._sensor_api.begin_multipart_upload(filesize, sensor.sensor_id)
80
-
81
- with open(file_path, "rb") as f:
82
- for i in range(0, len(upload.parts), chunk_size):
83
- group: List[MultipartUploadPart] = upload.parts[i : i + chunk_size]
84
- for part in group:
85
- read_amount = part.end_offset - part.start_offset
86
- f.seek(int(part.start_offset))
87
- part_data = f.read(int(read_amount))
88
-
89
- # The SaaS multipart upload returns a upload url for each part
90
- # So there is no specified route
91
- self._api_client.call_api(
92
- "PUT",
93
- part.upload_url,
94
- body=part_data,
95
- header_params={"Content-Type": "application/octet-binary"},
96
- )
97
-
98
- self._sensor_api.complete_multipart_upload(
99
- sensor.sensor_id, upload.upload_id
100
- )
101
-
102
- self._model_scan_api.scan_model(sensor.sensor_id)
103
- else:
104
- with open(file_path, "rb") as f:
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(model_name=model_name)
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
- threads=threads,
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
- threads=threads,
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
- ) -> List[ScanResults]:
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(self, *, model_name: str) -> ScanResults:
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
- if self.is_saas:
358
- model = self._model_api.get(model_name=model_name)
359
- sensor_id = model.sensor_id
360
- else:
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
- scan_results_v2 = self._model_scan_api.scan_status(sensor_id)
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.from_scanresultsv2(
366
- scan_results_v2=scan_results_v2, sensor_id=sensor_id
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
- ) -> List[ScanResults]:
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
- return [
406
- self.scan_file(
407
- model_name=str(file),
408
- model_path=file,
409
- threads=threads,
410
- chunk_size=chunk_size,
411
- wait_for_results=wait_for_results,
412
- )
413
- for file in files
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
+ )
@@ -1 +1 @@
1
- VERSION = "0.1.2"
1
+ VERSION = "1.1.0"
@@ -1,10 +1,10 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: hiddenlayer-sdk
3
- Version: 0.1.2
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: Apache 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 >=1.10.9
226
- Requires-Dist: python-dateutil >=2.0.0
227
- Requires-Dist: numpy >=1.0.0
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 >=0.2.2 ; extra == 'dev'
235
- Requires-Dist: pytest ; extra == 'dev'
236
- Requires-Dist: pandas ; extra == 'dev'
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: huggingface-hub ; extra == 'hf'
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
- If you are using the Enterprise version of the production, you can instantiate the `HiddenlayerServiceClient` as follows:
280
+ ### Scanning Models
281
281
 
282
282
  ```python
283
- from hiddenlayer import HiddenlayerServiceClient
284
-
285
- hl_client = HiddenlayerServiceClient(
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
- ### Scanning Models
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.