hiddenlayer-sdk 2.0.10__py3-none-any.whl → 3.0.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.
Files changed (204) hide show
  1. hiddenlayer/__init__.py +109 -114
  2. hiddenlayer/_base_client.py +1995 -0
  3. hiddenlayer/_client.py +761 -0
  4. hiddenlayer/_compat.py +219 -0
  5. hiddenlayer/_constants.py +14 -0
  6. hiddenlayer/_exceptions.py +108 -0
  7. hiddenlayer/_files.py +123 -0
  8. hiddenlayer/_models.py +835 -0
  9. hiddenlayer/_oauth2.py +118 -0
  10. hiddenlayer/_qs.py +150 -0
  11. hiddenlayer/_resource.py +43 -0
  12. hiddenlayer/_response.py +832 -0
  13. hiddenlayer/_streaming.py +333 -0
  14. hiddenlayer/_types.py +260 -0
  15. hiddenlayer/_utils/__init__.py +64 -0
  16. hiddenlayer/_utils/_compat.py +45 -0
  17. hiddenlayer/_utils/_datetime_parse.py +136 -0
  18. hiddenlayer/_utils/_logs.py +25 -0
  19. hiddenlayer/_utils/_proxy.py +65 -0
  20. hiddenlayer/_utils/_reflection.py +42 -0
  21. hiddenlayer/_utils/_resources_proxy.py +24 -0
  22. hiddenlayer/_utils/_streams.py +12 -0
  23. hiddenlayer/_utils/_sync.py +86 -0
  24. hiddenlayer/_utils/_transform.py +457 -0
  25. hiddenlayer/_utils/_typing.py +156 -0
  26. hiddenlayer/_utils/_utils.py +421 -0
  27. hiddenlayer/_version.py +4 -0
  28. hiddenlayer/lib/.keep +4 -0
  29. hiddenlayer/lib/__init__.py +6 -0
  30. hiddenlayer/lib/community_scan.py +174 -0
  31. hiddenlayer/lib/model_scan.py +752 -0
  32. hiddenlayer/lib/scan_utils.py +142 -0
  33. hiddenlayer/pagination.py +127 -0
  34. hiddenlayer/resources/__init__.py +75 -0
  35. hiddenlayer/resources/interactions.py +205 -0
  36. hiddenlayer/resources/models/__init__.py +33 -0
  37. hiddenlayer/resources/models/cards.py +259 -0
  38. hiddenlayer/resources/models/models.py +284 -0
  39. hiddenlayer/resources/prompt_analyzer.py +207 -0
  40. hiddenlayer/resources/scans/__init__.py +61 -0
  41. hiddenlayer/resources/scans/jobs.py +499 -0
  42. hiddenlayer/resources/scans/results.py +169 -0
  43. hiddenlayer/resources/scans/scans.py +166 -0
  44. hiddenlayer/resources/scans/upload/__init__.py +33 -0
  45. hiddenlayer/resources/scans/upload/file.py +279 -0
  46. hiddenlayer/resources/scans/upload/upload.py +340 -0
  47. hiddenlayer/resources/sensors.py +575 -0
  48. hiddenlayer/types/__init__.py +16 -0
  49. hiddenlayer/types/interaction_analyze_params.py +62 -0
  50. hiddenlayer/types/interaction_analyze_response.py +199 -0
  51. hiddenlayer/types/model_retrieve_response.py +50 -0
  52. hiddenlayer/types/models/__init__.py +6 -0
  53. hiddenlayer/types/models/card_list_params.py +65 -0
  54. hiddenlayer/types/models/card_list_response.py +50 -0
  55. hiddenlayer/types/prompt_analyzer_create_params.py +23 -0
  56. hiddenlayer/types/prompt_analyzer_create_response.py +381 -0
  57. hiddenlayer/types/scans/__init__.py +14 -0
  58. hiddenlayer/types/scans/job_list_params.py +75 -0
  59. hiddenlayer/types/scans/job_list_response.py +22 -0
  60. hiddenlayer/types/scans/job_request_params.py +49 -0
  61. hiddenlayer/types/scans/job_retrieve_params.py +16 -0
  62. hiddenlayer/types/scans/result_sarif_response.py +7 -0
  63. hiddenlayer/types/scans/scan_job.py +46 -0
  64. hiddenlayer/types/scans/scan_report.py +367 -0
  65. hiddenlayer/types/scans/upload/__init__.py +6 -0
  66. hiddenlayer/types/scans/upload/file_add_response.py +24 -0
  67. hiddenlayer/types/scans/upload/file_complete_response.py +12 -0
  68. hiddenlayer/types/scans/upload_complete_all_response.py +12 -0
  69. hiddenlayer/types/scans/upload_start_params.py +34 -0
  70. hiddenlayer/types/scans/upload_start_response.py +12 -0
  71. hiddenlayer/types/sensor_create_params.py +24 -0
  72. hiddenlayer/types/sensor_create_response.py +33 -0
  73. hiddenlayer/types/sensor_query_params.py +39 -0
  74. hiddenlayer/types/sensor_query_response.py +43 -0
  75. hiddenlayer/types/sensor_retrieve_response.py +33 -0
  76. hiddenlayer/types/sensor_update_params.py +20 -0
  77. hiddenlayer/types/sensor_update_response.py +9 -0
  78. hiddenlayer_sdk-3.0.0.dist-info/METADATA +431 -0
  79. hiddenlayer_sdk-3.0.0.dist-info/RECORD +82 -0
  80. {hiddenlayer_sdk-2.0.10.dist-info → hiddenlayer_sdk-3.0.0.dist-info}/WHEEL +1 -2
  81. {hiddenlayer_sdk-2.0.10.dist-info → hiddenlayer_sdk-3.0.0.dist-info}/licenses/LICENSE +1 -1
  82. hiddenlayer/sdk/constants.py +0 -26
  83. hiddenlayer/sdk/exceptions.py +0 -12
  84. hiddenlayer/sdk/models.py +0 -58
  85. hiddenlayer/sdk/rest/__init__.py +0 -135
  86. hiddenlayer/sdk/rest/api/__init__.py +0 -10
  87. hiddenlayer/sdk/rest/api/aidr_predictive_api.py +0 -308
  88. hiddenlayer/sdk/rest/api/health_api.py +0 -272
  89. hiddenlayer/sdk/rest/api/model_api.py +0 -559
  90. hiddenlayer/sdk/rest/api/model_supply_chain_api.py +0 -4063
  91. hiddenlayer/sdk/rest/api/readiness_api.py +0 -272
  92. hiddenlayer/sdk/rest/api/sensor_api.py +0 -1432
  93. hiddenlayer/sdk/rest/api_client.py +0 -770
  94. hiddenlayer/sdk/rest/api_response.py +0 -21
  95. hiddenlayer/sdk/rest/configuration.py +0 -445
  96. hiddenlayer/sdk/rest/exceptions.py +0 -199
  97. hiddenlayer/sdk/rest/models/__init__.py +0 -113
  98. hiddenlayer/sdk/rest/models/address.py +0 -110
  99. hiddenlayer/sdk/rest/models/artifact.py +0 -155
  100. hiddenlayer/sdk/rest/models/artifact_change.py +0 -108
  101. hiddenlayer/sdk/rest/models/artifact_content.py +0 -101
  102. hiddenlayer/sdk/rest/models/artifact_location.py +0 -109
  103. hiddenlayer/sdk/rest/models/attachment.py +0 -129
  104. hiddenlayer/sdk/rest/models/begin_multi_file_upload200_response.py +0 -87
  105. hiddenlayer/sdk/rest/models/begin_multipart_file_upload200_response.py +0 -97
  106. hiddenlayer/sdk/rest/models/begin_multipart_file_upload200_response_parts_inner.py +0 -94
  107. hiddenlayer/sdk/rest/models/code_flow.py +0 -113
  108. hiddenlayer/sdk/rest/models/configuration_override.py +0 -108
  109. hiddenlayer/sdk/rest/models/conversion.py +0 -114
  110. hiddenlayer/sdk/rest/models/create_sensor_request.py +0 -95
  111. hiddenlayer/sdk/rest/models/edge.py +0 -108
  112. hiddenlayer/sdk/rest/models/edge_traversal.py +0 -122
  113. hiddenlayer/sdk/rest/models/errors_inner.py +0 -91
  114. hiddenlayer/sdk/rest/models/exception.py +0 -113
  115. hiddenlayer/sdk/rest/models/external_properties.py +0 -273
  116. hiddenlayer/sdk/rest/models/external_property_file_reference.py +0 -102
  117. hiddenlayer/sdk/rest/models/external_property_file_references.py +0 -240
  118. hiddenlayer/sdk/rest/models/file_details_v3.py +0 -139
  119. hiddenlayer/sdk/rest/models/file_result_v3.py +0 -117
  120. hiddenlayer/sdk/rest/models/file_scan_report_v3.py +0 -132
  121. hiddenlayer/sdk/rest/models/file_scan_reports_v3.py +0 -95
  122. hiddenlayer/sdk/rest/models/fix.py +0 -113
  123. hiddenlayer/sdk/rest/models/get_condensed_model_scan_reports200_response.py +0 -102
  124. hiddenlayer/sdk/rest/models/graph.py +0 -123
  125. hiddenlayer/sdk/rest/models/graph_traversal.py +0 -97
  126. hiddenlayer/sdk/rest/models/inventory_v3.py +0 -101
  127. hiddenlayer/sdk/rest/models/invocation.py +0 -199
  128. hiddenlayer/sdk/rest/models/location.py +0 -146
  129. hiddenlayer/sdk/rest/models/location_inner.py +0 -138
  130. hiddenlayer/sdk/rest/models/location_relationship.py +0 -107
  131. hiddenlayer/sdk/rest/models/logical_location.py +0 -104
  132. hiddenlayer/sdk/rest/models/message.py +0 -92
  133. hiddenlayer/sdk/rest/models/mitre_atlas_inner.py +0 -110
  134. hiddenlayer/sdk/rest/models/model.py +0 -103
  135. hiddenlayer/sdk/rest/models/model_inventory_info.py +0 -103
  136. hiddenlayer/sdk/rest/models/model_version.py +0 -97
  137. hiddenlayer/sdk/rest/models/multi_file_upload_request_v3.py +0 -97
  138. hiddenlayer/sdk/rest/models/multiformat_message_string.py +0 -95
  139. hiddenlayer/sdk/rest/models/node.py +0 -122
  140. hiddenlayer/sdk/rest/models/notification.py +0 -157
  141. hiddenlayer/sdk/rest/models/notify_model_scan_completed200_response.py +0 -87
  142. hiddenlayer/sdk/rest/models/paged_response_with_total.py +0 -94
  143. hiddenlayer/sdk/rest/models/pagination_v3.py +0 -95
  144. hiddenlayer/sdk/rest/models/physical_location.py +0 -94
  145. hiddenlayer/sdk/rest/models/problem_details.py +0 -103
  146. hiddenlayer/sdk/rest/models/property_bag.py +0 -101
  147. hiddenlayer/sdk/rest/models/rectangle.py +0 -110
  148. hiddenlayer/sdk/rest/models/region.py +0 -127
  149. hiddenlayer/sdk/rest/models/replacement.py +0 -103
  150. hiddenlayer/sdk/rest/models/reporting_configuration.py +0 -113
  151. hiddenlayer/sdk/rest/models/reporting_descriptor.py +0 -162
  152. hiddenlayer/sdk/rest/models/reporting_descriptor_reference.py +0 -103
  153. hiddenlayer/sdk/rest/models/reporting_descriptor_relationship.py +0 -115
  154. hiddenlayer/sdk/rest/models/result.py +0 -312
  155. hiddenlayer/sdk/rest/models/result_provenance.py +0 -133
  156. hiddenlayer/sdk/rest/models/rule_details_inner.py +0 -102
  157. hiddenlayer/sdk/rest/models/run.py +0 -318
  158. hiddenlayer/sdk/rest/models/run_automation_details.py +0 -129
  159. hiddenlayer/sdk/rest/models/sarif210.py +0 -123
  160. hiddenlayer/sdk/rest/models/scan_create_request.py +0 -87
  161. hiddenlayer/sdk/rest/models/scan_detection_v3.py +0 -159
  162. hiddenlayer/sdk/rest/models/scan_detection_v31.py +0 -158
  163. hiddenlayer/sdk/rest/models/scan_header_v3.py +0 -129
  164. hiddenlayer/sdk/rest/models/scan_job.py +0 -115
  165. hiddenlayer/sdk/rest/models/scan_job_access.py +0 -97
  166. hiddenlayer/sdk/rest/models/scan_model_details_v3.py +0 -99
  167. hiddenlayer/sdk/rest/models/scan_model_details_v31.py +0 -97
  168. hiddenlayer/sdk/rest/models/scan_model_ids_v3.py +0 -89
  169. hiddenlayer/sdk/rest/models/scan_report_v3.py +0 -139
  170. hiddenlayer/sdk/rest/models/scan_results_map_v3.py +0 -105
  171. hiddenlayer/sdk/rest/models/scan_results_v3.py +0 -120
  172. hiddenlayer/sdk/rest/models/security_posture.py +0 -89
  173. hiddenlayer/sdk/rest/models/sensor.py +0 -100
  174. hiddenlayer/sdk/rest/models/sensor_query_response.py +0 -101
  175. hiddenlayer/sdk/rest/models/sensor_sor_model_card_query_response.py +0 -101
  176. hiddenlayer/sdk/rest/models/sensor_sor_model_card_response.py +0 -127
  177. hiddenlayer/sdk/rest/models/sensor_sor_query_filter.py +0 -108
  178. hiddenlayer/sdk/rest/models/sensor_sor_query_request.py +0 -109
  179. hiddenlayer/sdk/rest/models/special_locations.py +0 -97
  180. hiddenlayer/sdk/rest/models/stack.py +0 -113
  181. hiddenlayer/sdk/rest/models/stack_frame.py +0 -104
  182. hiddenlayer/sdk/rest/models/submission_response.py +0 -95
  183. hiddenlayer/sdk/rest/models/submission_v2.py +0 -109
  184. hiddenlayer/sdk/rest/models/suppression.py +0 -133
  185. hiddenlayer/sdk/rest/models/thread_flow.py +0 -144
  186. hiddenlayer/sdk/rest/models/thread_flow_location.py +0 -166
  187. hiddenlayer/sdk/rest/models/tool.py +0 -107
  188. hiddenlayer/sdk/rest/models/tool_component.py +0 -251
  189. hiddenlayer/sdk/rest/models/tool_component_reference.py +0 -108
  190. hiddenlayer/sdk/rest/models/translation_metadata.py +0 -110
  191. hiddenlayer/sdk/rest/models/validation_error_model.py +0 -99
  192. hiddenlayer/sdk/rest/models/version_control_details.py +0 -108
  193. hiddenlayer/sdk/rest/models/web_request.py +0 -112
  194. hiddenlayer/sdk/rest/models/web_response.py +0 -112
  195. hiddenlayer/sdk/rest/rest.py +0 -257
  196. hiddenlayer/sdk/services/__init__.py +0 -0
  197. hiddenlayer/sdk/services/aidr_predictive.py +0 -130
  198. hiddenlayer/sdk/services/model_scan.py +0 -505
  199. hiddenlayer/sdk/utils.py +0 -92
  200. hiddenlayer/sdk/version.py +0 -1
  201. hiddenlayer_sdk-2.0.10.dist-info/METADATA +0 -368
  202. hiddenlayer_sdk-2.0.10.dist-info/RECORD +0 -126
  203. hiddenlayer_sdk-2.0.10.dist-info/top_level.txt +0 -1
  204. /hiddenlayer/{sdk/__init__.py → py.typed} +0 -0
@@ -1,130 +0,0 @@
1
- import base64
2
- from datetime import datetime
3
- from typing import Any, Dict, List, Optional, Union
4
-
5
- import numpy as np
6
-
7
- from hiddenlayer.sdk.exceptions import SensorDoesNotExistError
8
- from hiddenlayer.sdk.rest.api import AidrPredictiveApi
9
- from hiddenlayer.sdk.rest.api.sensor_api import SensorApi
10
- from hiddenlayer.sdk.rest.api_client import ApiClient
11
- from hiddenlayer.sdk.rest.models import (
12
- SubmissionResponse,
13
- SubmissionV2,
14
- )
15
- from hiddenlayer.sdk.rest.models.create_sensor_request import CreateSensorRequest
16
- from hiddenlayer.sdk.rest.models.sensor import Sensor
17
- from hiddenlayer.sdk.rest.models.sensor_sor_query_filter import SensorSORQueryFilter
18
- from hiddenlayer.sdk.rest.models.sensor_sor_query_request import SensorSORQueryRequest
19
-
20
-
21
- class AIDRPredictive:
22
- def __init__(self, api_client: ApiClient) -> None:
23
- self._sensor_api = SensorApi(api_client=api_client)
24
- self._aidr_predictive = AidrPredictiveApi(api_client=api_client)
25
-
26
- def submit_vectors(
27
- self,
28
- *,
29
- sensor_id: str,
30
- requester_id: str,
31
- input_vectors: Union[List[float], np.ndarray],
32
- output: Union[List[float], np.ndarray],
33
- predictions: Optional[List[float]] = None,
34
- tags: Optional[List[str]] = None,
35
- metadata: Optional[Dict[str, Any]] = None,
36
- event_time: Optional[str] = None,
37
- ) -> SubmissionResponse:
38
- """
39
- Submit feature vectors and model outputs via the HiddenLayer API.
40
-
41
- :param sensor_id: Sensor id.
42
- :param requester_id: Custom identifier for the inbound request. This should be a value that can be used to identify individual users interacting with the model.
43
- :param input_vectors: Feature vectors for your model.
44
- :param output: Output vectors directly from your model.
45
- :param predictions: If you ran `np.argmax` or `np.argmin` or provided custom logic onto the model output.
46
- :param tags: Custom tags attached to the request.
47
- :param metadata: Custom metadata attached to the request.
48
- :param event_time: Time when the features and outputs were created, defaults to now.
49
-
50
- :returns: Submission Response
51
- """
52
-
53
- input_vectors = (
54
- np.array(input_vectors)
55
- if isinstance(input_vectors, list)
56
- else input_vectors
57
- )
58
- output = np.array(output) if isinstance(output, list) else output
59
-
60
- # Output vectors need to be at least 2d or AIDR will fail silently
61
- output = output.reshape(-1, 1) if len(output.shape) == 1 else output
62
-
63
- input_layer = base64.b64encode(input_vectors.tobytes()).decode()
64
- output_layer = base64.b64encode(output.tobytes()).decode()
65
-
66
- return self._aidr_predictive.submit_vectors(
67
- SubmissionV2(
68
- metadata=metadata if metadata else {},
69
- tags=tags if tags else [],
70
- sensor_id=sensor_id,
71
- requester_id=requester_id,
72
- input_layer=input_layer,
73
- input_layer_dtype=str(input_vectors.dtype),
74
- input_layer_shape=list(input_vectors.shape),
75
- output_layer=output_layer,
76
- output_layer_dtype=str(output.dtype),
77
- output_layer_shape=list(output.shape),
78
- predictions=predictions,
79
- event_time=event_time
80
- if event_time
81
- else str(datetime.now().isoformat()),
82
- )
83
- )
84
-
85
- def create_sensor(self, *, sensor_name: str) -> Sensor:
86
- """
87
- Creates a sensor in the HiddenLayer Platform.
88
-
89
- :params sensor_name: Name of the sensor
90
-
91
- :returns: HiddenLayer Sensor
92
- """
93
- return self._sensor_api.create_sensor(
94
- CreateSensorRequest(plaintext_name=sensor_name)
95
- )
96
-
97
- def get_sensor(self, *, sensor_name: str) -> Sensor:
98
- """
99
- Gets a HiddenLayer sensor object.
100
-
101
- :params sensor_name: Name of the sensor
102
-
103
- :returns: HiddenLayer Sensor
104
- """
105
-
106
- return self._get_sensor_by_name(sensor_name=sensor_name)
107
-
108
- def _get_sensor_by_name(self, *, sensor_name: str) -> Sensor:
109
- """
110
- Gets a model sensor by name.
111
-
112
- :param sensor_name: Name of the model.
113
-
114
- :returns: HiddenLayer Model object
115
- """
116
-
117
- sensors = self._sensor_api.query_sensor(
118
- sensor_sor_query_request=SensorSORQueryRequest(
119
- filter=SensorSORQueryFilter(plaintext_name=sensor_name)
120
- )
121
- )
122
-
123
- if not sensors.results or len(sensors.results) == 0:
124
- msg = f"ModSensorel {sensor_name} does not exist"
125
-
126
- raise SensorDoesNotExistError(msg)
127
-
128
- sensors.results.sort(key=lambda x: x.version, reverse=True)
129
-
130
- return sensors.results[0]
@@ -1,505 +0,0 @@
1
- import os
2
- import random
3
- import tempfile
4
- import time
5
- import zipfile
6
- from pathlib import Path
7
- from typing import Callable, List, Optional, Union
8
-
9
- from hiddenlayer.sdk.constants import CommunityScanSource, ScanStatus
10
- from hiddenlayer.sdk.models import EmptyScanResults, ScanResults
11
- from hiddenlayer.sdk.rest.api import ModelSupplyChainApi
12
- from hiddenlayer.sdk.rest.api_client import ApiClient
13
- from hiddenlayer.sdk.rest.exceptions import NotFoundException, UnauthorizedException
14
- from hiddenlayer.sdk.rest.models import (
15
- MultiFileUploadRequestV3,
16
- ScanJob,
17
- ScanJobAccess,
18
- ScanModelDetailsV31,
19
- )
20
- from hiddenlayer.sdk.utils import filter_path_objects
21
-
22
- EXCLUDE_FILE_TYPES = [
23
- "*.txt",
24
- "*.md",
25
- "*.lock",
26
- ".gitattributes",
27
- ".git",
28
- ".git/*",
29
- "*/.git",
30
- "**/.git/**",
31
- ]
32
-
33
-
34
- class ModelScanAPI:
35
- def __init__(
36
- self,
37
- api_client: ApiClient,
38
- refresh_token_func: Optional[Callable[[], str]] = None,
39
- ) -> None:
40
- self._api_client = api_client
41
- self._model_supply_chain_api = ModelSupplyChainApi(api_client=api_client)
42
- self._refresh_token_func = refresh_token_func
43
-
44
- def community_scan(
45
- self,
46
- model_name: str,
47
- model_path: str,
48
- model_source: CommunityScanSource,
49
- model_version: str = "main",
50
- wait_for_results: bool = True,
51
- request_source="API Upload",
52
- origin: str = "",
53
- ) -> ScanResults:
54
- """
55
- Scan a model available at a remote location using the HiddenLayer Model Scanner.
56
-
57
- :param model_name: Name of the model to be shown on the HiddenLayer UI.
58
- :param model_path: Path to the model file in the remote location, e.g. a presigned S3 URL
59
- :param model_source: type of remote location where the model is stored.
60
- :param wait_for_results: True whether to wait for the scan to finish, defaults to True.
61
- :param model_version: Version of the model to be shown on the HiddenLayer UI.
62
-
63
- :returns: Scan Results
64
- """
65
- scan_job = ScanJob(
66
- access=ScanJobAccess(source=model_source),
67
- inventory=ScanModelDetailsV31(
68
- model_name=model_name,
69
- model_version=model_version,
70
- requested_scan_location=model_path,
71
- requesting_entity="hiddenlayer-python-sdk",
72
- request_source=request_source,
73
- origin=origin,
74
- ),
75
- )
76
- result = self._model_supply_chain_api.create_scan_job(scan_job)
77
- scan_id = result.scan_id
78
- if scan_id is None:
79
- raise Exception("scan_id must have a value")
80
- if wait_for_results:
81
- return self._wait_for_scan_results(scan_id=scan_id)
82
- else:
83
- return ScanResults.from_scanreportv3(scan_report_v3=result)
84
-
85
- def scan_file(
86
- self,
87
- *,
88
- model_name: str,
89
- model_path: Union[str, os.PathLike],
90
- model_version: str = "1",
91
- wait_for_results: bool = True,
92
- request_source="API Upload",
93
- origin: str = "",
94
- ) -> ScanResults:
95
- """
96
- Scan a local model file using the HiddenLayer Model Scanner.
97
-
98
- :param model_name: Name of the model to be shown on the HiddenLayer UI
99
- :param model_path: Local path to the model file.
100
- :param model_version: Version of the model to be shown on the HiddenLayer UI.
101
- :param chunk_size: Number of chunks of the file to upload at once, defaults to 4.
102
- :param wait_for_results: True whether to wait for the scan to finish, defaults to True.
103
-
104
- :returns: Scan Results
105
- """
106
-
107
- file_path = Path(model_path)
108
-
109
- request = MultiFileUploadRequestV3(
110
- model_name=model_name,
111
- model_version=model_version,
112
- requesting_entity="hiddenlayer-python-sdk",
113
- request_source=request_source,
114
- origin=origin,
115
- )
116
- response = self._model_supply_chain_api.begin_multi_file_upload(
117
- multi_file_upload_request_v3=request
118
- )
119
- scan_id = response.scan_id
120
- if scan_id is None:
121
- raise Exception("scan_id must have a value")
122
-
123
- self._scan_file(scan_id=scan_id, file_path=file_path)
124
-
125
- self._model_supply_chain_api.complete_multi_file_upload(scan_id=scan_id)
126
- scan_results = self._wait_for_scan_results(scan_id=scan_id)
127
- scan_results.file_name = file_path.name
128
- scan_results.file_path = str(file_path)
129
-
130
- return scan_results
131
-
132
- def scan_s3_model(
133
- self,
134
- *,
135
- model_name: str,
136
- bucket: str,
137
- key: str,
138
- model_version: str = "1",
139
- s3_client: Optional[object] = None,
140
- wait_for_results: bool = True,
141
- request_source="API Upload",
142
- ) -> ScanResults:
143
- """
144
- Scan a model file on S3.
145
-
146
- :param model_name: Name of the model to be shown on the HiddenLayer UI.
147
- :param bucket: Name of the s3 bucket where the model file is stored.
148
- :param key: Path to the model file on s3.
149
- :param model_version: Version of the model to be shown on the HiddenLayer UI.
150
- :param wait_for_results: True whether to wait for the scan to finish, defaults to True.
151
- :param s3_client: boto3 s3 client.
152
- :param chunk_size: Number of chunks of the file to upload at once, defaults to 4.
153
- :param wait_for_results: True whether to wait for the scan to finish, defaults to True.
154
-
155
- :returns: Scan Results
156
-
157
- :examples:
158
- .. code-block:: python
159
-
160
- hl_client.model_scanner.scan_s3_model(
161
- model_name="your-model-name",
162
- bucket="s3_bucket",
163
- key="path/to/file"
164
- )
165
- """
166
- try:
167
- import boto3 # type: ignore
168
- except ImportError:
169
- raise ImportError("Python package boto3 is not installed.")
170
-
171
- if not s3_client:
172
- s3_client = boto3.client("s3")
173
-
174
- file_name = key.split("/")[-1]
175
-
176
- try:
177
- s3_client.download_file(bucket, key, f"/tmp/{file_name}") # type: ignore
178
- except Exception as e:
179
- raise RuntimeError(f"Couldn't download model s3://{bucket}/{key}: {e}")
180
-
181
- return self.scan_file(
182
- model_path=f"/tmp/{file_name}",
183
- model_name=model_name,
184
- model_version=model_version,
185
- wait_for_results=wait_for_results,
186
- request_source=request_source,
187
- origin="S3",
188
- )
189
-
190
- def scan_azure_blob_model(
191
- self,
192
- *,
193
- model_name: str,
194
- account_url: str,
195
- container: str,
196
- blob: str,
197
- model_version: str = "1",
198
- blob_service_client: Optional[object] = None,
199
- credential: Optional[object] = None,
200
- wait_for_results: bool = True,
201
- request_source="API Upload",
202
- ) -> ScanResults:
203
- """
204
- Scan a model file on Azure Blob Storage.
205
-
206
- :param model_name: Name of the model to be shown on the HiddenLayer UI.
207
- :param account_url: Azure Blob url of where the file is stored.
208
- :param container: Azure Blob container containing the model file.
209
- :param blob: Path to the model file inside the Azure blob container.
210
- :param model_version: Version of the model to be shown on the HiddenLayer UI.
211
- :param blob_service_client: BlobServiceClient object. Defaults to creating one using DefaultCredential().
212
- :param credential: Credential to be passed to the BlobServiceClient object, can be a credential object, SAS key, etc.
213
- Defaults to `DefaultCredential`
214
- :param chunk_size: Number of chunks of the file to upload at once, defaults to 4.
215
- :param wait_for_results: True whether to wait for the scan to finish, defaults to True.
216
-
217
- :returns: Scan Results
218
-
219
- :examples:
220
- .. code-block:: python
221
-
222
- hl_client.model_scanner.scan_azure_blob_model(
223
- model_name="your-model-name",
224
- account_url="https://<storageaccountname>.blob.core.windows.net",
225
- container="container_name",
226
- blob="path/to/file.bin",
227
- credential="?<sas_key>" # If using a SAS key and not DefaultCredentials
228
- )
229
- """
230
- try:
231
- from azure.identity import DefaultAzureCredential
232
- except ImportError:
233
- raise ImportError("Python package azure-identity is not installed.")
234
-
235
- try:
236
- from azure.storage.blob import BlobServiceClient
237
- except ImportError:
238
- raise ImportError("Python package azure-storage-blob is not installed.")
239
-
240
- if not credential:
241
- credential = DefaultAzureCredential()
242
-
243
- if not blob_service_client:
244
- blob_service_client = BlobServiceClient(account_url, credential=credential) # type: ignore
245
-
246
- file_name = blob.split("/")[-1]
247
-
248
- blob_client = blob_service_client.get_blob_client( # type: ignore
249
- container=container, blob=blob
250
- )
251
-
252
- try:
253
- with open(os.path.join("/tmp", file_name), "wb") as f:
254
- download_stream = blob_client.download_blob()
255
- f.write(download_stream.readall())
256
-
257
- except Exception as e:
258
- raise RuntimeError(
259
- f"Couldn't download model {account_url}, {container}, {blob}: {e}"
260
- )
261
-
262
- return self.scan_file(
263
- model_path=f"/tmp/{file_name}",
264
- model_name=model_name,
265
- model_version=model_version,
266
- wait_for_results=wait_for_results,
267
- request_source=request_source,
268
- origin="Azure Blob Storage",
269
- )
270
-
271
- def scan_huggingface_model(
272
- self,
273
- *,
274
- repo_id: str,
275
- model_name: Optional[str] = None,
276
- # model_id: str,
277
- # HF parameters
278
- revision: Optional[str] = None,
279
- local_dir: str = "/tmp",
280
- allow_file_patterns: Optional[List[str]] = None,
281
- ignore_file_patterns: Optional[List[str]] = None,
282
- force_download: bool = False,
283
- hf_token: Optional[Union[str, bool]] = None,
284
- wait_for_results: bool = True,
285
- request_source="API Upload",
286
- ) -> ScanResults:
287
- """
288
- Scans a model on HuggingFace.
289
-
290
- Note: Requires the `huggingface_hub` pip package to be installed.
291
-
292
- :param repo_id: The HuggingFace repository id.
293
- :param revision: An optional Git revision id which can be a branch name, a tag, or a commit hash.
294
- :param local_dir: If provided, the downloaded files will be placed under this directory.
295
- :param allow_file_patterns: If provided, only files matching at least one pattern are scanned.
296
- :param ignore_file_patterns: If provided, files matching any of the patterns are not scanned.
297
- :param force_download: Whether the file should be downloaded even if it already exists in the local cache.
298
- :param hf_token: A token to be used for the download.
299
- If True, the token is read from the HuggingFace config folder.
300
- If a string, it’s used as the authentication token.
301
- :param chunk_size: Number of chunks of the file to upload at once, defaults to 4.
302
- :param wait_for_results: True whether to wait for the scan to finish, defaults to True.
303
-
304
- :returns: List of ScanResults
305
- """
306
- try:
307
- from huggingface_hub import snapshot_download
308
- except ImportError:
309
- raise ImportError("Python package huggingface_hub is not installed.")
310
-
311
- local_dir = f"/tmp/{repo_id}" if local_dir == "/tmp" else local_dir
312
- ignore_file_patterns = (
313
- EXCLUDE_FILE_TYPES + ignore_file_patterns
314
- if ignore_file_patterns
315
- else EXCLUDE_FILE_TYPES
316
- )
317
-
318
- snapshot_download(
319
- repo_id,
320
- revision=revision,
321
- allow_patterns=allow_file_patterns,
322
- ignore_patterns=ignore_file_patterns,
323
- local_dir=local_dir,
324
- local_dir_use_symlinks=False,
325
- cache_dir=local_dir,
326
- force_download=force_download,
327
- token=hf_token,
328
- )
329
-
330
- if revision is None:
331
- revision = "1"
332
-
333
- return self.scan_folder(
334
- model_name=model_name or repo_id,
335
- model_version=revision,
336
- path=local_dir,
337
- allow_file_patterns=allow_file_patterns,
338
- ignore_file_patterns=ignore_file_patterns,
339
- wait_for_results=wait_for_results,
340
- request_source=request_source,
341
- origin="Hugging Face",
342
- )
343
-
344
- def get_scan_results(self, *, scan_id: str) -> ScanResults:
345
- """
346
- Get results from a model scan.
347
-
348
- :param model_name: Name of the model.
349
- :param model_version: Version of the model. When the model version is not specified, the scan results for the latest version will be returned.
350
-
351
- :returns: Scan results.
352
- """
353
- retry = False
354
- while True:
355
- try:
356
- scan_report = self._model_supply_chain_api.get_scan_results(scan_id)
357
- break
358
- except NotFoundException:
359
- return EmptyScanResults()
360
- except UnauthorizedException as e:
361
- if not retry and self._refresh_token_func:
362
- new_token = self._refresh_token_func()
363
- self._api_client.configuration.access_token = new_token
364
- self._api_client = ApiClient(self._api_client.configuration)
365
- retry = True
366
- else:
367
- raise e
368
-
369
- return ScanResults.from_scanreportv3(scan_report_v3=scan_report)
370
-
371
- def get_sarif_results(
372
- self,
373
- *,
374
- scan_id: str,
375
- ) -> Optional[str]:
376
- """
377
- Get sarif results from a model scan.
378
-
379
- :param model_name: Name of the model.
380
- :param model_version: Version of the model. When the model version is not specified, the scan results for the latest version will be returned.
381
-
382
- :returns: Scan results.
383
- """
384
-
385
- # Unfortunately, the generated code for the API doesn't directly support modifying the Accept header
386
- # in order to enable us to get the Sarif results
387
- # Here we will reach in to the request serialization process. The 2nd element in the tuple is the headers
388
- # where we will modify the Accept header to application/sarif+json
389
- request = self._model_supply_chain_api._get_scan_results_serialize(
390
- scan_id, None, None, None, None, 0
391
- )
392
- request[2]["Accept"] = "application/sarif+json"
393
- response = self._api_client.call_api(*request)
394
- response.read()
395
-
396
- if response.data is None:
397
- return None
398
-
399
- return response.data.decode()
400
-
401
- def scan_folder(
402
- self,
403
- *,
404
- model_name: str,
405
- path: Union[str, os.PathLike],
406
- model_version: str = "1",
407
- allow_file_patterns: Optional[List[str]] = None,
408
- ignore_file_patterns: Optional[List[str]] = None,
409
- wait_for_results: bool = True,
410
- request_source="API Upload",
411
- origin: str = "",
412
- ) -> ScanResults:
413
- """
414
- Submits all files in a directory and its sub directories to be scanned.
415
-
416
- :param model_name: Name of the model to be shown on the HiddenLayer UI.
417
- :param path: Path to the folder on disk to be scanned.
418
- :param model_version: Version of the model to be shown on the HiddenLayer UI.
419
- :param allow_file_patterns: If provided, only files matching at least one pattern are scanned.
420
- :param ignore_file_patterns: If provided, files matching any of the patterns are not scanned.
421
- :param chunk_size: Number of chunks of the file to upload at once, defaults to 4.
422
- :param wait_for_results: True whether to wait for the scan to finish, defaults to True.
423
-
424
- :returns: List of ScanResults
425
- """
426
-
427
- model_path = Path(path)
428
-
429
- request = MultiFileUploadRequestV3(
430
- model_name=model_name,
431
- model_version=model_version,
432
- requesting_entity="hiddenlayer-python-sdk",
433
- request_source=request_source,
434
- origin=origin,
435
- )
436
- response = self._model_supply_chain_api.begin_multi_file_upload(
437
- multi_file_upload_request_v3=request
438
- )
439
- scan_id = response.scan_id
440
- if scan_id is None:
441
- raise Exception("scan_id must have a value")
442
-
443
- ignore_file_patterns = (
444
- EXCLUDE_FILE_TYPES + ignore_file_patterns
445
- if ignore_file_patterns
446
- else EXCLUDE_FILE_TYPES
447
- )
448
-
449
- files = filter_path_objects(
450
- model_path.rglob("*"),
451
- allow_patterns=allow_file_patterns,
452
- ignore_patterns=ignore_file_patterns,
453
- )
454
-
455
- for file in files:
456
- self._scan_file(scan_id=scan_id, file_path=Path(file))
457
-
458
- self._model_supply_chain_api.complete_multi_file_upload(scan_id=scan_id)
459
- scan_results = self._wait_for_scan_results(scan_id=scan_id)
460
-
461
- return scan_results
462
-
463
- def _scan_file(self, *, scan_id: str, file_path: Path):
464
- filesize = file_path.stat().st_size
465
- upload = self._model_supply_chain_api.begin_multipart_file_upload(
466
- scan_id=str(scan_id), file_name=str(file_path), file_content_length=filesize
467
- )
468
-
469
- with open(file_path, "rb") as f:
470
- for part in upload.parts:
471
- if part.start_offset is None:
472
- raise Exception("part must have a start_offset")
473
- if part.end_offset is not None:
474
- read_amount = part.end_offset - part.start_offset
475
- else:
476
- read_amount = None
477
- f.seek(part.start_offset)
478
- part_data = f.read(read_amount)
479
- self._api_client.call_api(
480
- "PUT",
481
- part.upload_url,
482
- body=part_data,
483
- header_params={"Content-Type": "application/octet-binary"},
484
- )
485
- self._model_supply_chain_api.complete_multipart_file_upload(
486
- scan_id=scan_id, file_id=upload.upload_id
487
- )
488
-
489
- def _wait_for_scan_results(self, *, scan_id: str):
490
- scan_results = self.get_scan_results(scan_id=scan_id)
491
-
492
- base_delay = 0.1 # seconds
493
- retries = 0
494
- print(f"scan status: {scan_results.status}")
495
- while scan_results.status not in [ScanStatus.DONE, ScanStatus.FAILED]:
496
- retries += 1
497
- delay = base_delay * 2**retries + random.uniform(
498
- 0, 1
499
- ) # exponential back off retry
500
- delay = min(delay, 30)
501
- time.sleep(delay)
502
- scan_results = self.get_scan_results(scan_id=scan_id)
503
- print(f"scan status: {scan_results.status}")
504
-
505
- return scan_results