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.
- hiddenlayer/__init__.py +109 -114
- hiddenlayer/_base_client.py +1995 -0
- hiddenlayer/_client.py +761 -0
- hiddenlayer/_compat.py +219 -0
- hiddenlayer/_constants.py +14 -0
- hiddenlayer/_exceptions.py +108 -0
- hiddenlayer/_files.py +123 -0
- hiddenlayer/_models.py +835 -0
- hiddenlayer/_oauth2.py +118 -0
- hiddenlayer/_qs.py +150 -0
- hiddenlayer/_resource.py +43 -0
- hiddenlayer/_response.py +832 -0
- hiddenlayer/_streaming.py +333 -0
- hiddenlayer/_types.py +260 -0
- hiddenlayer/_utils/__init__.py +64 -0
- hiddenlayer/_utils/_compat.py +45 -0
- hiddenlayer/_utils/_datetime_parse.py +136 -0
- hiddenlayer/_utils/_logs.py +25 -0
- hiddenlayer/_utils/_proxy.py +65 -0
- hiddenlayer/_utils/_reflection.py +42 -0
- hiddenlayer/_utils/_resources_proxy.py +24 -0
- hiddenlayer/_utils/_streams.py +12 -0
- hiddenlayer/_utils/_sync.py +86 -0
- hiddenlayer/_utils/_transform.py +457 -0
- hiddenlayer/_utils/_typing.py +156 -0
- hiddenlayer/_utils/_utils.py +421 -0
- hiddenlayer/_version.py +4 -0
- hiddenlayer/lib/.keep +4 -0
- hiddenlayer/lib/__init__.py +6 -0
- hiddenlayer/lib/community_scan.py +174 -0
- hiddenlayer/lib/model_scan.py +752 -0
- hiddenlayer/lib/scan_utils.py +142 -0
- hiddenlayer/pagination.py +127 -0
- hiddenlayer/resources/__init__.py +75 -0
- hiddenlayer/resources/interactions.py +205 -0
- hiddenlayer/resources/models/__init__.py +33 -0
- hiddenlayer/resources/models/cards.py +259 -0
- hiddenlayer/resources/models/models.py +284 -0
- hiddenlayer/resources/prompt_analyzer.py +207 -0
- hiddenlayer/resources/scans/__init__.py +61 -0
- hiddenlayer/resources/scans/jobs.py +499 -0
- hiddenlayer/resources/scans/results.py +169 -0
- hiddenlayer/resources/scans/scans.py +166 -0
- hiddenlayer/resources/scans/upload/__init__.py +33 -0
- hiddenlayer/resources/scans/upload/file.py +279 -0
- hiddenlayer/resources/scans/upload/upload.py +340 -0
- hiddenlayer/resources/sensors.py +575 -0
- hiddenlayer/types/__init__.py +16 -0
- hiddenlayer/types/interaction_analyze_params.py +62 -0
- hiddenlayer/types/interaction_analyze_response.py +199 -0
- hiddenlayer/types/model_retrieve_response.py +50 -0
- hiddenlayer/types/models/__init__.py +6 -0
- hiddenlayer/types/models/card_list_params.py +65 -0
- hiddenlayer/types/models/card_list_response.py +50 -0
- hiddenlayer/types/prompt_analyzer_create_params.py +23 -0
- hiddenlayer/types/prompt_analyzer_create_response.py +381 -0
- hiddenlayer/types/scans/__init__.py +14 -0
- hiddenlayer/types/scans/job_list_params.py +75 -0
- hiddenlayer/types/scans/job_list_response.py +22 -0
- hiddenlayer/types/scans/job_request_params.py +49 -0
- hiddenlayer/types/scans/job_retrieve_params.py +16 -0
- hiddenlayer/types/scans/result_sarif_response.py +7 -0
- hiddenlayer/types/scans/scan_job.py +46 -0
- hiddenlayer/types/scans/scan_report.py +367 -0
- hiddenlayer/types/scans/upload/__init__.py +6 -0
- hiddenlayer/types/scans/upload/file_add_response.py +24 -0
- hiddenlayer/types/scans/upload/file_complete_response.py +12 -0
- hiddenlayer/types/scans/upload_complete_all_response.py +12 -0
- hiddenlayer/types/scans/upload_start_params.py +34 -0
- hiddenlayer/types/scans/upload_start_response.py +12 -0
- hiddenlayer/types/sensor_create_params.py +24 -0
- hiddenlayer/types/sensor_create_response.py +33 -0
- hiddenlayer/types/sensor_query_params.py +39 -0
- hiddenlayer/types/sensor_query_response.py +43 -0
- hiddenlayer/types/sensor_retrieve_response.py +33 -0
- hiddenlayer/types/sensor_update_params.py +20 -0
- hiddenlayer/types/sensor_update_response.py +9 -0
- hiddenlayer_sdk-3.0.0.dist-info/METADATA +431 -0
- hiddenlayer_sdk-3.0.0.dist-info/RECORD +82 -0
- {hiddenlayer_sdk-2.0.10.dist-info → hiddenlayer_sdk-3.0.0.dist-info}/WHEEL +1 -2
- {hiddenlayer_sdk-2.0.10.dist-info → hiddenlayer_sdk-3.0.0.dist-info}/licenses/LICENSE +1 -1
- hiddenlayer/sdk/constants.py +0 -26
- hiddenlayer/sdk/exceptions.py +0 -12
- hiddenlayer/sdk/models.py +0 -58
- hiddenlayer/sdk/rest/__init__.py +0 -135
- hiddenlayer/sdk/rest/api/__init__.py +0 -10
- hiddenlayer/sdk/rest/api/aidr_predictive_api.py +0 -308
- hiddenlayer/sdk/rest/api/health_api.py +0 -272
- hiddenlayer/sdk/rest/api/model_api.py +0 -559
- hiddenlayer/sdk/rest/api/model_supply_chain_api.py +0 -4063
- hiddenlayer/sdk/rest/api/readiness_api.py +0 -272
- hiddenlayer/sdk/rest/api/sensor_api.py +0 -1432
- hiddenlayer/sdk/rest/api_client.py +0 -770
- hiddenlayer/sdk/rest/api_response.py +0 -21
- hiddenlayer/sdk/rest/configuration.py +0 -445
- hiddenlayer/sdk/rest/exceptions.py +0 -199
- hiddenlayer/sdk/rest/models/__init__.py +0 -113
- hiddenlayer/sdk/rest/models/address.py +0 -110
- hiddenlayer/sdk/rest/models/artifact.py +0 -155
- hiddenlayer/sdk/rest/models/artifact_change.py +0 -108
- hiddenlayer/sdk/rest/models/artifact_content.py +0 -101
- hiddenlayer/sdk/rest/models/artifact_location.py +0 -109
- hiddenlayer/sdk/rest/models/attachment.py +0 -129
- hiddenlayer/sdk/rest/models/begin_multi_file_upload200_response.py +0 -87
- hiddenlayer/sdk/rest/models/begin_multipart_file_upload200_response.py +0 -97
- hiddenlayer/sdk/rest/models/begin_multipart_file_upload200_response_parts_inner.py +0 -94
- hiddenlayer/sdk/rest/models/code_flow.py +0 -113
- hiddenlayer/sdk/rest/models/configuration_override.py +0 -108
- hiddenlayer/sdk/rest/models/conversion.py +0 -114
- hiddenlayer/sdk/rest/models/create_sensor_request.py +0 -95
- hiddenlayer/sdk/rest/models/edge.py +0 -108
- hiddenlayer/sdk/rest/models/edge_traversal.py +0 -122
- hiddenlayer/sdk/rest/models/errors_inner.py +0 -91
- hiddenlayer/sdk/rest/models/exception.py +0 -113
- hiddenlayer/sdk/rest/models/external_properties.py +0 -273
- hiddenlayer/sdk/rest/models/external_property_file_reference.py +0 -102
- hiddenlayer/sdk/rest/models/external_property_file_references.py +0 -240
- hiddenlayer/sdk/rest/models/file_details_v3.py +0 -139
- hiddenlayer/sdk/rest/models/file_result_v3.py +0 -117
- hiddenlayer/sdk/rest/models/file_scan_report_v3.py +0 -132
- hiddenlayer/sdk/rest/models/file_scan_reports_v3.py +0 -95
- hiddenlayer/sdk/rest/models/fix.py +0 -113
- hiddenlayer/sdk/rest/models/get_condensed_model_scan_reports200_response.py +0 -102
- hiddenlayer/sdk/rest/models/graph.py +0 -123
- hiddenlayer/sdk/rest/models/graph_traversal.py +0 -97
- hiddenlayer/sdk/rest/models/inventory_v3.py +0 -101
- hiddenlayer/sdk/rest/models/invocation.py +0 -199
- hiddenlayer/sdk/rest/models/location.py +0 -146
- hiddenlayer/sdk/rest/models/location_inner.py +0 -138
- hiddenlayer/sdk/rest/models/location_relationship.py +0 -107
- hiddenlayer/sdk/rest/models/logical_location.py +0 -104
- hiddenlayer/sdk/rest/models/message.py +0 -92
- hiddenlayer/sdk/rest/models/mitre_atlas_inner.py +0 -110
- hiddenlayer/sdk/rest/models/model.py +0 -103
- hiddenlayer/sdk/rest/models/model_inventory_info.py +0 -103
- hiddenlayer/sdk/rest/models/model_version.py +0 -97
- hiddenlayer/sdk/rest/models/multi_file_upload_request_v3.py +0 -97
- hiddenlayer/sdk/rest/models/multiformat_message_string.py +0 -95
- hiddenlayer/sdk/rest/models/node.py +0 -122
- hiddenlayer/sdk/rest/models/notification.py +0 -157
- hiddenlayer/sdk/rest/models/notify_model_scan_completed200_response.py +0 -87
- hiddenlayer/sdk/rest/models/paged_response_with_total.py +0 -94
- hiddenlayer/sdk/rest/models/pagination_v3.py +0 -95
- hiddenlayer/sdk/rest/models/physical_location.py +0 -94
- hiddenlayer/sdk/rest/models/problem_details.py +0 -103
- hiddenlayer/sdk/rest/models/property_bag.py +0 -101
- hiddenlayer/sdk/rest/models/rectangle.py +0 -110
- hiddenlayer/sdk/rest/models/region.py +0 -127
- hiddenlayer/sdk/rest/models/replacement.py +0 -103
- hiddenlayer/sdk/rest/models/reporting_configuration.py +0 -113
- hiddenlayer/sdk/rest/models/reporting_descriptor.py +0 -162
- hiddenlayer/sdk/rest/models/reporting_descriptor_reference.py +0 -103
- hiddenlayer/sdk/rest/models/reporting_descriptor_relationship.py +0 -115
- hiddenlayer/sdk/rest/models/result.py +0 -312
- hiddenlayer/sdk/rest/models/result_provenance.py +0 -133
- hiddenlayer/sdk/rest/models/rule_details_inner.py +0 -102
- hiddenlayer/sdk/rest/models/run.py +0 -318
- hiddenlayer/sdk/rest/models/run_automation_details.py +0 -129
- hiddenlayer/sdk/rest/models/sarif210.py +0 -123
- hiddenlayer/sdk/rest/models/scan_create_request.py +0 -87
- hiddenlayer/sdk/rest/models/scan_detection_v3.py +0 -159
- hiddenlayer/sdk/rest/models/scan_detection_v31.py +0 -158
- hiddenlayer/sdk/rest/models/scan_header_v3.py +0 -129
- hiddenlayer/sdk/rest/models/scan_job.py +0 -115
- hiddenlayer/sdk/rest/models/scan_job_access.py +0 -97
- hiddenlayer/sdk/rest/models/scan_model_details_v3.py +0 -99
- hiddenlayer/sdk/rest/models/scan_model_details_v31.py +0 -97
- hiddenlayer/sdk/rest/models/scan_model_ids_v3.py +0 -89
- hiddenlayer/sdk/rest/models/scan_report_v3.py +0 -139
- hiddenlayer/sdk/rest/models/scan_results_map_v3.py +0 -105
- hiddenlayer/sdk/rest/models/scan_results_v3.py +0 -120
- hiddenlayer/sdk/rest/models/security_posture.py +0 -89
- hiddenlayer/sdk/rest/models/sensor.py +0 -100
- hiddenlayer/sdk/rest/models/sensor_query_response.py +0 -101
- hiddenlayer/sdk/rest/models/sensor_sor_model_card_query_response.py +0 -101
- hiddenlayer/sdk/rest/models/sensor_sor_model_card_response.py +0 -127
- hiddenlayer/sdk/rest/models/sensor_sor_query_filter.py +0 -108
- hiddenlayer/sdk/rest/models/sensor_sor_query_request.py +0 -109
- hiddenlayer/sdk/rest/models/special_locations.py +0 -97
- hiddenlayer/sdk/rest/models/stack.py +0 -113
- hiddenlayer/sdk/rest/models/stack_frame.py +0 -104
- hiddenlayer/sdk/rest/models/submission_response.py +0 -95
- hiddenlayer/sdk/rest/models/submission_v2.py +0 -109
- hiddenlayer/sdk/rest/models/suppression.py +0 -133
- hiddenlayer/sdk/rest/models/thread_flow.py +0 -144
- hiddenlayer/sdk/rest/models/thread_flow_location.py +0 -166
- hiddenlayer/sdk/rest/models/tool.py +0 -107
- hiddenlayer/sdk/rest/models/tool_component.py +0 -251
- hiddenlayer/sdk/rest/models/tool_component_reference.py +0 -108
- hiddenlayer/sdk/rest/models/translation_metadata.py +0 -110
- hiddenlayer/sdk/rest/models/validation_error_model.py +0 -99
- hiddenlayer/sdk/rest/models/version_control_details.py +0 -108
- hiddenlayer/sdk/rest/models/web_request.py +0 -112
- hiddenlayer/sdk/rest/models/web_response.py +0 -112
- hiddenlayer/sdk/rest/rest.py +0 -257
- hiddenlayer/sdk/services/__init__.py +0 -0
- hiddenlayer/sdk/services/aidr_predictive.py +0 -130
- hiddenlayer/sdk/services/model_scan.py +0 -505
- hiddenlayer/sdk/utils.py +0 -92
- hiddenlayer/sdk/version.py +0 -1
- hiddenlayer_sdk-2.0.10.dist-info/METADATA +0 -368
- hiddenlayer_sdk-2.0.10.dist-info/RECORD +0 -126
- hiddenlayer_sdk-2.0.10.dist-info/top_level.txt +0 -1
- /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
|