pangea-sdk 4.1.0__py3-none-any.whl → 4.3.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.
@@ -5,22 +5,62 @@ from __future__ import annotations
5
5
 
6
6
  import enum
7
7
  from typing import Dict, List, NewType, Optional, Union
8
+ from warnings import warn
8
9
 
9
- from pydantic import BaseModel, ConfigDict
10
+ from typing_extensions import deprecated
10
11
 
11
12
  import pangea.services.intel as im
13
+ from pangea.deprecated import pangea_deprecated
12
14
  from pangea.response import APIRequestModel, APIResponseModel, PangeaResponseResult
13
15
  from pangea.services.vault.models.common import JWK, JWKec, JWKrsa
14
16
 
15
17
  Scopes = NewType("Scopes", List[str])
16
18
 
17
19
 
18
- class Profile(BaseModel):
19
- first_name: str
20
- last_name: Optional[str] = None
21
- phone: Optional[str] = None
22
-
23
- model_config = ConfigDict(extra="allow")
20
+ class Profile(Dict[str, str]):
21
+ @property
22
+ def first_name(self) -> str:
23
+ warn(
24
+ '`Profile.first_name` is deprecated. Use `Profile["first_name"]` instead.', DeprecationWarning, stacklevel=2
25
+ )
26
+ return self["first_name"]
27
+
28
+ @first_name.setter
29
+ def first_name(self, value: str) -> None:
30
+ warn(
31
+ '`Profile.first_name` is deprecated. Use `Profile["first_name"]` instead.', DeprecationWarning, stacklevel=2
32
+ )
33
+ self["first_name"] = value
34
+
35
+ @property
36
+ def last_name(self) -> str:
37
+ warn('`Profile.last_name` is deprecated. Use `Profile["last_name"]` instead.', DeprecationWarning, stacklevel=2)
38
+ return self["last_name"]
39
+
40
+ @last_name.setter
41
+ def last_name(self, value: str) -> None:
42
+ warn('`Profile.last_name` is deprecated. Use `Profile["last_name"]` instead.', DeprecationWarning, stacklevel=2)
43
+ self["last_name"] = value
44
+
45
+ @property
46
+ def phone(self) -> str:
47
+ warn('`Profile.phone` is deprecated. Use `Profile["phone"]` instead.', DeprecationWarning, stacklevel=2)
48
+ return self["phone"]
49
+
50
+ @phone.setter
51
+ def phone(self, value: str) -> None:
52
+ warn('`Profile.phone` is deprecated. Use `Profile["phone"]` instead.', DeprecationWarning, stacklevel=2)
53
+ self["phone"] = value
54
+
55
+ @deprecated("`Profile.model_dump()` is deprecated. `Profile` is already a `dict[str, str]`.")
56
+ @pangea_deprecated(reason="`Profile` is already a `dict[str, str]`.")
57
+ def model_dump(self, *, exclude_none: bool = False) -> dict[str, str]:
58
+ warn(
59
+ "`Profile.model_dump()` is deprecated. `Profile` is already a `dict[str, str]`.",
60
+ DeprecationWarning,
61
+ stacklevel=2,
62
+ )
63
+ return self
24
64
 
25
65
 
26
66
  class ClientPasswordChangeRequest(APIRequestModel):
@@ -65,7 +105,7 @@ class SessionToken(PangeaResponseResult):
65
105
  identity: str
66
106
  email: str
67
107
  scopes: Optional[Scopes] = None
68
- profile: Profile
108
+ profile: Union[Profile, Dict[str, str]]
69
109
  created_at: str
70
110
  intelligence: Optional[Intelligence] = None
71
111
 
@@ -156,12 +196,43 @@ class UserListOrderBy(enum.Enum):
156
196
 
157
197
 
158
198
  class Authenticator(APIResponseModel):
199
+ """Authenticator."""
200
+
159
201
  id: str
202
+ """An ID for an authenticator."""
203
+
160
204
  type: str
205
+ """An authentication mechanism."""
206
+
161
207
  enabled: bool
208
+ """Enabled."""
209
+
162
210
  provider: Optional[str] = None
211
+ """Provider."""
212
+
213
+ provider_name: Optional[str] = None
214
+ """Provider name."""
215
+
163
216
  rpid: Optional[str] = None
217
+ """RPID."""
218
+
164
219
  phase: Optional[str] = None
220
+ """Phase."""
221
+
222
+ enrolling_browser: Optional[str] = None
223
+ """Enrolling browser."""
224
+
225
+ enrolling_ip: Optional[str] = None
226
+ """Enrolling IP."""
227
+
228
+ created_at: str
229
+ """A time in ISO-8601 format."""
230
+
231
+ updated_at: str
232
+ """A time in ISO-8601 format."""
233
+
234
+ state: Optional[str] = None
235
+ """State."""
165
236
 
166
237
 
167
238
  class User(PangeaResponseResult):
@@ -174,7 +245,7 @@ class User(PangeaResponseResult):
174
245
  username: str
175
246
  """A username."""
176
247
 
177
- profile: Profile
248
+ profile: Union[Profile, Dict[str, str]]
178
249
  """A user profile as a collection of string properties."""
179
250
 
180
251
  verified: bool
@@ -207,7 +278,7 @@ class UserCreateRequest(APIRequestModel):
207
278
  email: str
208
279
  """An email address."""
209
280
 
210
- profile: Profile
281
+ profile: Union[Profile, Dict[str, str]]
211
282
  """A user profile as a collection of string properties."""
212
283
 
213
284
  username: Optional[str] = None
@@ -397,7 +468,7 @@ class UserProfileGetResult(User):
397
468
 
398
469
 
399
470
  class UserProfileUpdateRequest(APIRequestModel):
400
- profile: Profile
471
+ profile: Union[Profile, Dict[str, str]]
401
472
  """Updates to a user profile."""
402
473
 
403
474
  id: Optional[str] = None
@@ -485,6 +556,7 @@ class UserAuthenticatorsListRequest(APIRequestModel):
485
556
 
486
557
  class UserAuthenticatorsListResult(PangeaResponseResult):
487
558
  authenticators: List[Authenticator] = []
559
+ """A list of authenticators."""
488
560
 
489
561
 
490
562
  class FlowCompleteRequest(APIRequestModel):
@@ -583,7 +655,7 @@ class FlowUpdateDataPassword(APIRequestModel):
583
655
 
584
656
 
585
657
  class FlowUpdateDataProfile(APIRequestModel):
586
- profile: Profile
658
+ profile: Union[Profile, Dict[str, str]]
587
659
 
588
660
 
589
661
  class FlowUpdateDataProvisionalEnrollment(APIRequestModel):
@@ -705,7 +777,7 @@ class SessionItem(APIResponseModel):
705
777
  expire: str
706
778
  email: str
707
779
  scopes: Optional[Scopes] = None
708
- profile: Profile
780
+ profile: Union[Profile, Dict[str, str]]
709
781
  created_at: str
710
782
  active_token: Optional[SessionToken] = None
711
783
 
pangea/services/authz.py CHANGED
@@ -132,6 +132,7 @@ class ListResourcesRequest(APIRequestModel):
132
132
  type: str
133
133
  action: str
134
134
  subject: Subject
135
+ attributes: Optional[Dict[str, Any]] = None
135
136
 
136
137
 
137
138
  class ListResourcesResult(PangeaResponseResult):
@@ -141,6 +142,7 @@ class ListResourcesResult(PangeaResponseResult):
141
142
  class ListSubjectsRequest(APIRequestModel):
142
143
  resource: Resource
143
144
  action: str
145
+ attributes: Optional[Dict[str, Any]] = None
144
146
 
145
147
 
146
148
  class ListSubjectsResult(PangeaResponseResult):
@@ -278,7 +280,7 @@ class AuthZ(ServiceBase):
278
280
  action: str,
279
281
  subject: Subject,
280
282
  debug: Optional[bool] = None,
281
- attributes: Optional[Dict[str, Union[int, str]]] = None,
283
+ attributes: Optional[Dict[str, Any]] = None,
282
284
  ) -> PangeaResponse[CheckResult]:
283
285
  """Perform a check request.
284
286
 
@@ -289,7 +291,7 @@ class AuthZ(ServiceBase):
289
291
  action (str): The action to check.
290
292
  subject (Subject): The subject to check.
291
293
  debug (Optional[bool]): Setting this value to True will provide a detailed analysis of the check.
292
- attributes (Optional[Dict[str, Union[int, str]]]): Additional attributes for the check.
294
+ attributes (Optional[Dict[str, Any]]): Additional attributes for the check.
293
295
 
294
296
  Raises:
295
297
  PangeaAPIException: If an API Error happens.
@@ -311,8 +313,10 @@ class AuthZ(ServiceBase):
311
313
  input_data = CheckRequest(resource=resource, action=action, subject=subject, debug=debug, attributes=attributes)
312
314
  return self.request.post("v1/check", CheckResult, data=input_data.model_dump(exclude_none=True))
313
315
 
314
- def list_resources(self, type: str, action: str, subject: Subject) -> PangeaResponse[ListResourcesResult]:
315
- """List resources.
316
+ def list_resources(
317
+ self, type: str, action: str, subject: Subject, attributes: Optional[Dict[str, Any]] = None
318
+ ) -> PangeaResponse[ListResourcesResult]:
319
+ """List resources. (Beta)
316
320
 
317
321
  Given a type, action, and subject, list all the resources in the
318
322
  type that the subject has access to the action with.
@@ -321,6 +325,7 @@ class AuthZ(ServiceBase):
321
325
  type (str): The type to filter resources.
322
326
  action (str): The action to filter resources.
323
327
  subject (Subject): The subject to filter resources.
328
+ attributes (Optional[Dict[str, Any]]): A JSON object of attribute data.
324
329
 
325
330
  Raises:
326
331
  PangeaAPIException: If an API Error happens.
@@ -338,13 +343,15 @@ class AuthZ(ServiceBase):
338
343
  )
339
344
  """
340
345
 
341
- input_data = ListResourcesRequest(type=type, action=action, subject=subject)
346
+ input_data = ListResourcesRequest(type=type, action=action, subject=subject, attributes=attributes)
342
347
  return self.request.post(
343
348
  "v1/list-resources", ListResourcesResult, data=input_data.model_dump(exclude_none=True)
344
349
  )
345
350
 
346
- def list_subjects(self, resource: Resource, action: str) -> PangeaResponse[ListSubjectsResult]:
347
- """List subjects.
351
+ def list_subjects(
352
+ self, resource: Resource, action: str, attributes: Optional[Dict[str, Any]] = None
353
+ ) -> PangeaResponse[ListSubjectsResult]:
354
+ """List subjects. (Beta)
348
355
 
349
356
  Given a resource and an action, return the list of subjects who have
350
357
  access to the action for the given resource.
@@ -352,6 +359,7 @@ class AuthZ(ServiceBase):
352
359
  Args:
353
360
  resource (Resource): The resource to filter subjects.
354
361
  action (str): The action to filter subjects.
362
+ attributes (Optional[Dict[str, Any]]): A JSON object of attribute data.
355
363
 
356
364
  Raises:
357
365
  PangeaAPIException: If an API Error happens.
@@ -368,5 +376,5 @@ class AuthZ(ServiceBase):
368
376
  )
369
377
  """
370
378
 
371
- input_data = ListSubjectsRequest(resource=resource, action=action)
379
+ input_data = ListSubjectsRequest(resource=resource, action=action, attributes=attributes)
372
380
  return self.request.post("v1/list-subjects", ListSubjectsResult, data=input_data.model_dump(exclude_none=True))
@@ -11,22 +11,25 @@ from pangea.utils import FileUploadParams, get_file_upload_params
11
11
 
12
12
 
13
13
  class FileScanRequest(APIRequestModel):
14
- """
15
- File Scan request data
16
-
17
- provider (str, optional): Provider of the information. Default provider defined by the configuration.
18
- verbose (bool, optional): Echo back the parameters of the API in the response
19
- raw (bool, optional): Return additional details from the provider.
20
- """
14
+ """File Scan request data."""
21
15
 
22
16
  verbose: Optional[bool] = None
17
+ """Echo back the parameters of the API in the response."""
18
+
23
19
  raw: Optional[bool] = None
20
+ """Return additional details from the provider."""
21
+
24
22
  provider: Optional[str] = None
23
+ """Provider of the information. Default provider defined by the configuration."""
24
+
25
25
  size: Optional[int] = None
26
26
  crc32c: Optional[str] = None
27
27
  sha256: Optional[str] = None
28
28
  source_url: Optional[str] = None
29
+ """A URL where the file to be scanned can be downloaded."""
30
+
29
31
  transfer_method: TransferMethod = TransferMethod.POST_URL
32
+ """The transfer method used to upload the file data."""
30
33
 
31
34
 
32
35
  class FileScanData(PangeaResponseResult):
@@ -71,7 +74,6 @@ class FileScan(ServiceBase):
71
74
  """
72
75
 
73
76
  service_name = "file-scan"
74
- version = "v1"
75
77
 
76
78
  def file_scan(
77
79
  self,
@@ -92,12 +94,14 @@ class FileScan(ServiceBase):
92
94
  OperationId: file_scan_post_v1_scan
93
95
 
94
96
  Args:
95
- file (io.BufferedReader, optional): file to be scanned (should be opened with read permissions and in binary format)
96
97
  file_path (str, optional): filepath to be opened and scanned
98
+ file (io.BufferedReader, optional): file to be scanned (should be opened with read permissions and in binary format)
97
99
  verbose (bool, optional): Echo the API parameters in the response
98
100
  raw (bool, optional): Include raw data from this provider
99
101
  provider (str, optional): Scan file using this provider
100
102
  sync_call (bool, optional): True to wait until server returns a result, False to return immediately and retrieve result asynchronously
103
+ transfer_method (TransferMethod, optional): Transfer method used to upload the file data.
104
+ source_url (str, optional): A URL where the Pangea APIs can fetch the contents of the input file.
101
105
 
102
106
  Raises:
103
107
  PangeaAPIException: If an API Error happens
@@ -118,6 +122,15 @@ class FileScan(ServiceBase):
118
122
  print(f"\\t{err.detail} \\n")
119
123
  """
120
124
 
125
+ if transfer_method == TransferMethod.SOURCE_URL and source_url is None:
126
+ raise ValueError("`source_url` argument is required when using `TransferMethod.SOURCE_URL`.")
127
+
128
+ if source_url is not None and transfer_method != TransferMethod.SOURCE_URL:
129
+ raise ValueError(
130
+ "`transfer_method` should be `TransferMethod.SOURCE_URL` when using the `source_url` argument."
131
+ )
132
+
133
+ files: Optional[List[Tuple]] = None
121
134
  if file or file_path:
122
135
  if file_path:
123
136
  file = open(file_path, "rb")
@@ -128,9 +141,9 @@ class FileScan(ServiceBase):
128
141
  size = params.size
129
142
  else:
130
143
  crc, sha, size = None, None, None
131
- files: List[Tuple] = [("upload", ("filename", file, "application/octet-stream"))]
132
- else:
133
- raise ValueError("Need to set file_path or file arguments")
144
+ files = [("upload", ("filename", file, "application/octet-stream"))]
145
+ elif source_url is None:
146
+ raise ValueError("Need to set one of `file_path`, `file`, or `source_url` arguments.")
134
147
 
135
148
  input = FileScanRequest(
136
149
  verbose=verbose,
@@ -143,7 +156,11 @@ class FileScan(ServiceBase):
143
156
  source_url=source_url,
144
157
  )
145
158
  data = input.model_dump(exclude_none=True)
146
- return self.request.post("v1/scan", FileScanResult, data=data, files=files, poll_result=sync_call)
159
+ try:
160
+ return self.request.post("v1/scan", FileScanResult, data=data, files=files, poll_result=sync_call)
161
+ finally:
162
+ if file_path and file:
163
+ file.close()
147
164
 
148
165
  def request_upload_url(
149
166
  self,
@@ -0,0 +1,335 @@
1
+ # Copyright 2022 Pangea Cyber Corporation
2
+ # Author: Pangea Cyber Corporation
3
+ from __future__ import annotations
4
+
5
+ import io
6
+ from typing import Dict, List, Optional, Tuple
7
+
8
+ from pangea.response import APIRequestModel, PangeaResponse, PangeaResponseResult, TransferMethod
9
+ from pangea.services.base import ServiceBase
10
+ from pangea.utils import FileUploadParams, get_file_upload_params
11
+
12
+
13
+ class SanitizeFile(APIRequestModel):
14
+ scan_provider: Optional[str] = None
15
+ """Provider to use for File Scan."""
16
+
17
+
18
+ class SanitizeContent(APIRequestModel):
19
+ url_intel: Optional[bool] = None
20
+ """Perform URL Intel lookup."""
21
+
22
+ url_intel_provider: Optional[str] = None
23
+ """Provider to use for URL Intel."""
24
+
25
+ domain_intel: Optional[bool] = None
26
+ """Perform Domain Intel lookup."""
27
+
28
+ domain_intel_provider: Optional[str] = None
29
+ """Provider to use for Domain Intel lookup."""
30
+
31
+ defang: Optional[bool] = None
32
+ """Defang external links."""
33
+
34
+ defang_threshold: Optional[int] = None
35
+ """Defang risk threshold."""
36
+
37
+ redact: Optional[bool] = None
38
+ """Redact sensitive content."""
39
+
40
+ remove_attachments: Optional[bool] = None
41
+ """Remove file attachments (PDF only)."""
42
+
43
+ remove_interactive: Optional[bool] = None
44
+ """Remove interactive content (PDF only)."""
45
+
46
+
47
+ class SanitizeShareOutput(APIRequestModel):
48
+ enabled: Optional[bool] = None
49
+ """Store Sanitized files to Pangea Secure Share."""
50
+
51
+ output_folder: Optional[str] = None
52
+ """
53
+ Store Sanitized files to this Secure Share folder (will be auto-created if
54
+ it does not exist)
55
+ """
56
+
57
+
58
+ class SanitizeRequest(APIRequestModel):
59
+ transfer_method: TransferMethod = TransferMethod.POST_URL
60
+ """The transfer method used to upload the file data."""
61
+
62
+ source_url: Optional[str] = None
63
+ """A URL where the file to be sanitized can be downloaded."""
64
+
65
+ share_id: Optional[str] = None
66
+ """A Pangea Secure Share ID where the file to be Sanitized is stored."""
67
+
68
+ file: Optional[SanitizeFile] = None
69
+ """File."""
70
+
71
+ content: Optional[SanitizeContent] = None
72
+ """Content."""
73
+
74
+ share_output: Optional[SanitizeShareOutput] = None
75
+ """Share output."""
76
+
77
+ size: Optional[int] = None
78
+ """The size (in bytes) of the file. If the upload doesn't match, the call will fail."""
79
+
80
+ crc32c: Optional[str] = None
81
+ """The CRC32C hash of the file data, which will be verified by the server if provided."""
82
+
83
+ sha256: Optional[str] = None
84
+ """The hexadecimal-encoded SHA256 hash of the file data, which will be verified by the server if provided."""
85
+
86
+ uploaded_file_name: Optional[str] = None
87
+ """Name of the user-uploaded file, required for transfer-method 'put-url' and 'post-url'."""
88
+
89
+
90
+ class DefangData(PangeaResponseResult):
91
+ external_urls_count: Optional[int] = None
92
+ """Number of external links found."""
93
+
94
+ external_domains_count: Optional[int] = None
95
+ """Number of external domains found."""
96
+
97
+ defanged_count: Optional[int] = None
98
+ """Number of items defanged per provided rules and detections."""
99
+
100
+ url_intel_summary: Optional[str] = None
101
+ """Processed N URLs: X are malicious, Y are suspicious, Z are unknown."""
102
+
103
+ domain_intel_summary: Optional[str] = None
104
+ """Processed N Domains: X are malicious, Y are suspicious, Z are unknown."""
105
+
106
+
107
+ class RedactData(PangeaResponseResult):
108
+ redaction_count: Optional[int] = None
109
+ """Number of items redacted"""
110
+
111
+ summary_counts: Dict = {}
112
+ """Summary counts."""
113
+
114
+
115
+ class CDR(PangeaResponseResult):
116
+ file_attachments_removed: Optional[int] = None
117
+ """Number of file attachments removed."""
118
+
119
+ interactive_contents_removed: Optional[int] = None
120
+ """Number of interactive content items removed."""
121
+
122
+
123
+ class SanitizeData(PangeaResponseResult):
124
+ defang: Optional[DefangData] = None
125
+ """Defang."""
126
+
127
+ redact: Optional[RedactData] = None
128
+ """Redact."""
129
+
130
+ malicious_file: Optional[bool] = None
131
+ """If the file scanned was malicious."""
132
+
133
+ cdr: Optional[CDR] = None
134
+ """Content Disarm and Reconstruction."""
135
+
136
+
137
+ class SanitizeResult(PangeaResponseResult):
138
+ dest_url: Optional[str] = None
139
+ """A URL where the Sanitized file can be downloaded."""
140
+
141
+ dest_share_id: Optional[str] = None
142
+ """Pangea Secure Share ID of the Sanitized file."""
143
+
144
+ data: SanitizeData
145
+ """Sanitize data."""
146
+
147
+ parameters: Dict = {}
148
+ """The parameters, which were passed in the request, echoed back."""
149
+
150
+
151
+ class Sanitize(ServiceBase):
152
+ """Sanitize service client.
153
+
154
+ Examples:
155
+ import os
156
+
157
+ # Pangea SDK
158
+ from pangea.config import PangeaConfig
159
+ from pangea.services import Sanitize
160
+
161
+ PANGEA_SANITIZE_TOKEN = os.getenv("PANGEA_SANITIZE_TOKEN")
162
+ config = PangeaConfig(domain="pangea.cloud")
163
+
164
+ sanitize = Sanitize(token=PANGEA_SANITIZE_TOKEN, config=config)
165
+ """
166
+
167
+ service_name = "sanitize"
168
+
169
+ def sanitize(
170
+ self,
171
+ transfer_method: TransferMethod = TransferMethod.POST_URL,
172
+ file_path: Optional[str] = None,
173
+ file: Optional[io.BufferedReader] = None,
174
+ source_url: Optional[str] = None,
175
+ share_id: Optional[str] = None,
176
+ file_scan: Optional[SanitizeFile] = None,
177
+ content: Optional[SanitizeContent] = None,
178
+ share_output: Optional[SanitizeShareOutput] = None,
179
+ size: Optional[int] = None,
180
+ crc32c: Optional[str] = None,
181
+ sha256: Optional[str] = None,
182
+ uploaded_file_name: Optional[str] = None,
183
+ sync_call: bool = True,
184
+ ) -> PangeaResponse[SanitizeResult]:
185
+ """
186
+ Sanitize
187
+
188
+ Apply file sanitization actions according to specified rules.
189
+
190
+ OperationId: sanitize_post_v1_sanitize
191
+
192
+ Args:
193
+ transfer_method: The transfer method used to upload the file data.
194
+ file_path: Path to file to sanitize.
195
+ file: File to sanitize.
196
+ source_url: A URL where the file to be sanitized can be downloaded.
197
+ share_id: A Pangea Secure Share ID where the file to be sanitized is stored.
198
+ file_scan: Options for File Scan.
199
+ content: Options for how the file should be sanitized.
200
+ share_output: Integration with Secure Share.
201
+ size: The size (in bytes) of the file. If the upload doesn't match, the call will fail.
202
+ crc32c: The CRC32C hash of the file data, which will be verified by the server if provided.
203
+ sha256: The hexadecimal-encoded SHA256 hash of the file data, which will be verified by the server if provided.
204
+ uploaded_file_name: Name of the user-uploaded file, required for `TransferMethod.PUT_URL` and `TransferMethod.POST_URL`.
205
+ sync_call: Whether or not to poll on HTTP/202.
206
+
207
+ Raises:
208
+ PangeaAPIException: If an API error happens.
209
+
210
+ Returns:
211
+ The sanitized file and information on the sanitization that was
212
+ performed.
213
+
214
+ Examples:
215
+ with open("/path/to/file.pdf", "rb") as f:
216
+ response = sanitize.sanitize(
217
+ file=f,
218
+ transfer_method=TransferMethod.POST_URL,
219
+ uploaded_file_name="uploaded_file",
220
+ )
221
+ """
222
+
223
+ if transfer_method == TransferMethod.SOURCE_URL and source_url is None:
224
+ raise ValueError("`source_url` argument is required when using `TransferMethod.SOURCE_URL`.")
225
+
226
+ if source_url is not None and transfer_method != TransferMethod.SOURCE_URL:
227
+ raise ValueError(
228
+ "`transfer_method` should be `TransferMethod.SOURCE_URL` when using the `source_url` argument."
229
+ )
230
+
231
+ files: Optional[List[Tuple]] = None
232
+ if file or file_path:
233
+ if file_path:
234
+ file = open(file_path, "rb")
235
+ if (
236
+ transfer_method == TransferMethod.POST_URL
237
+ and file
238
+ and (sha256 is None or crc32c is None or size is None)
239
+ ):
240
+ params = get_file_upload_params(file)
241
+ crc32c = params.crc_hex if crc32c is None else crc32c
242
+ sha256 = params.sha256_hex if sha256 is None else sha256
243
+ size = params.size if size is None else size
244
+ else:
245
+ crc32c, sha256, size = None, None, None
246
+ files = [("upload", ("filename", file, "application/octet-stream"))]
247
+ elif source_url is None:
248
+ raise ValueError("Need to set one of `file_path`, `file`, or `source_url` arguments.")
249
+
250
+ input = SanitizeRequest(
251
+ transfer_method=transfer_method,
252
+ source_url=source_url,
253
+ share_id=share_id,
254
+ file=file_scan,
255
+ content=content,
256
+ share_output=share_output,
257
+ crc32c=crc32c,
258
+ sha256=sha256,
259
+ size=size,
260
+ uploaded_file_name=uploaded_file_name,
261
+ )
262
+ data = input.model_dump(exclude_none=True)
263
+ try:
264
+ response = self.request.post("v1/sanitize", SanitizeResult, data=data, files=files, poll_result=sync_call)
265
+ finally:
266
+ if file_path and file is not None:
267
+ file.close()
268
+ return response
269
+
270
+ def request_upload_url(
271
+ self,
272
+ transfer_method: TransferMethod = TransferMethod.PUT_URL,
273
+ params: Optional[FileUploadParams] = None,
274
+ file_scan: Optional[SanitizeFile] = None,
275
+ content: Optional[SanitizeContent] = None,
276
+ share_output: Optional[SanitizeShareOutput] = None,
277
+ size: Optional[int] = None,
278
+ crc32c: Optional[str] = None,
279
+ sha256: Optional[str] = None,
280
+ uploaded_file_name: Optional[str] = None,
281
+ ) -> PangeaResponse[SanitizeResult]:
282
+ """
283
+ Sanitize via presigned URL
284
+
285
+ Apply file sanitization actions according to specified rules via a
286
+ [presigned URL](https://pangea.cloud/docs/api/transfer-methods).
287
+
288
+ OperationId: sanitize_post_v1_sanitize 2
289
+
290
+ Args:
291
+ transfer_method: The transfer method used to upload the file data.
292
+ params: File upload parameters.
293
+ file_scan: Options for File Scan.
294
+ content: Options for how the file should be sanitized.
295
+ share_output: Integration with Secure Share.
296
+ size: The size (in bytes) of the file. If the upload doesn't match, the call will fail.
297
+ crc32c: The CRC32C hash of the file data, which will be verified by the server if provided.
298
+ sha256: The hexadecimal-encoded SHA256 hash of the file data, which will be verified by the server if provided.
299
+ uploaded_file_name: Name of the user-uploaded file, required for `TransferMethod.PUT_URL` and `TransferMethod.POST_URL`.
300
+
301
+ Raises:
302
+ PangeaAPIException: If an API error happens.
303
+
304
+ Returns:
305
+ A presigned URL.
306
+
307
+ Examples:
308
+ presignedUrl = sanitize.request_upload_url(
309
+ transfer_method=TransferMethod.PUT_URL,
310
+ uploaded_file_name="uploaded_file",
311
+ )
312
+
313
+ # Upload file to `presignedUrl.accepted_result.put_url`.
314
+
315
+ # Poll for Sanitize's result.
316
+ response: PangeaResponse[SanitizeResult] = sanitize.poll_result(response=presignedUrl)
317
+ """
318
+
319
+ input = SanitizeRequest(
320
+ transfer_method=transfer_method,
321
+ file=file_scan,
322
+ content=content,
323
+ share_output=share_output,
324
+ crc32c=crc32c,
325
+ sha256=sha256,
326
+ size=size,
327
+ uploaded_file_name=uploaded_file_name,
328
+ )
329
+ if params is not None and (transfer_method == TransferMethod.POST_URL):
330
+ input.crc32c = params.crc_hex
331
+ input.sha256 = params.sha256_hex
332
+ input.size = params.size
333
+
334
+ data = input.model_dump(exclude_none=True)
335
+ return self.request.request_presigned_url("v1/sanitize", SanitizeResult, data=data)