pangea-sdk 3.8.0b4__py3-none-any.whl → 3.9.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.
- pangea/__init__.py +1 -2
- pangea/asyncio/request.py +14 -19
- pangea/asyncio/services/__init__.py +0 -2
- pangea/asyncio/services/audit.py +160 -14
- pangea/asyncio/services/authn.py +118 -79
- pangea/asyncio/services/authz.py +35 -50
- pangea/asyncio/services/intel.py +4 -4
- pangea/asyncio/services/redact.py +56 -2
- pangea/asyncio/services/vault.py +115 -4
- pangea/request.py +24 -21
- pangea/response.py +27 -28
- pangea/services/__init__.py +0 -2
- pangea/services/audit/audit.py +161 -16
- pangea/services/audit/models.py +53 -5
- pangea/services/authn/authn.py +77 -36
- pangea/services/authn/models.py +79 -0
- pangea/services/authz.py +47 -54
- pangea/services/base.py +23 -6
- pangea/services/file_scan.py +1 -0
- pangea/services/intel.py +2 -2
- pangea/services/redact.py +122 -2
- pangea/services/vault/models/common.py +115 -0
- pangea/services/vault/vault.py +117 -6
- pangea/utils.py +19 -91
- {pangea_sdk-3.8.0b4.dist-info → pangea_sdk-3.9.0.dist-info}/METADATA +6 -15
- pangea_sdk-3.9.0.dist-info/RECORD +46 -0
- pangea/asyncio/__init__.py +0 -1
- pangea/asyncio/file_uploader.py +0 -39
- pangea/asyncio/services/sanitize.py +0 -185
- pangea/asyncio/services/share.py +0 -573
- pangea/file_uploader.py +0 -35
- pangea/services/sanitize.py +0 -275
- pangea/services/share/file_format.py +0 -170
- pangea/services/share/share.py +0 -877
- pangea_sdk-3.8.0b4.dist-info/RECORD +0 -54
- {pangea_sdk-3.8.0b4.dist-info → pangea_sdk-3.9.0.dist-info}/WHEEL +0 -0
pangea/asyncio/services/intel.py
CHANGED
@@ -76,7 +76,7 @@ class FileIntelAsync(ServiceBaseAsync):
|
|
76
76
|
provider: Optional[str] = None,
|
77
77
|
verbose: Optional[bool] = None,
|
78
78
|
raw: Optional[bool] = None,
|
79
|
-
) -> PangeaResponse[m.
|
79
|
+
) -> PangeaResponse[m.FileReputationBulkResult]:
|
80
80
|
"""
|
81
81
|
Reputation check
|
82
82
|
|
@@ -182,7 +182,7 @@ class FileIntelAsync(ServiceBaseAsync):
|
|
182
182
|
hash = hash_256_filepath(filepath)
|
183
183
|
hashes.append(hash)
|
184
184
|
|
185
|
-
return await self.hash_reputation_bulk(
|
185
|
+
return await self.hash_reputation_bulk(
|
186
186
|
hashes=hashes, hash_type="sha256", verbose=verbose, raw=raw, provider=provider
|
187
187
|
)
|
188
188
|
|
@@ -251,7 +251,7 @@ class DomainIntelAsync(ServiceBaseAsync):
|
|
251
251
|
verbose: Optional[bool] = None,
|
252
252
|
raw: Optional[bool] = None,
|
253
253
|
provider: Optional[str] = None,
|
254
|
-
) -> PangeaResponse[m.
|
254
|
+
) -> PangeaResponse[m.DomainReputationBulkResult]:
|
255
255
|
"""
|
256
256
|
Reputation
|
257
257
|
|
@@ -373,7 +373,7 @@ class IpIntelAsync(ServiceBaseAsync):
|
|
373
373
|
|
374
374
|
async def reputation_bulk(
|
375
375
|
self, ips: List[str], verbose: Optional[bool] = None, raw: Optional[bool] = None, provider: Optional[str] = None
|
376
|
-
) -> PangeaResponse[m.
|
376
|
+
) -> PangeaResponse[m.IPReputationBulkResult]:
|
377
377
|
"""
|
378
378
|
Reputation
|
379
379
|
|
@@ -1,10 +1,12 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
+
from __future__ import annotations
|
3
4
|
|
4
5
|
from typing import Dict, List, Optional, Union
|
5
6
|
|
6
7
|
import pangea.services.redact as m
|
7
8
|
from pangea.asyncio.services.base import ServiceBaseAsync
|
9
|
+
from pangea.config import PangeaConfig
|
8
10
|
from pangea.response import PangeaResponse
|
9
11
|
|
10
12
|
|
@@ -35,7 +37,24 @@ class RedactAsync(ServiceBaseAsync):
|
|
35
37
|
|
36
38
|
service_name = "redact"
|
37
39
|
|
38
|
-
def __init__(
|
40
|
+
def __init__(
|
41
|
+
self, token: str, config: PangeaConfig | None = None, logger_name: str = "pangea", config_id: str | None = None
|
42
|
+
) -> None:
|
43
|
+
"""
|
44
|
+
Redact client
|
45
|
+
|
46
|
+
Initializes a new Redact client.
|
47
|
+
|
48
|
+
Args:
|
49
|
+
token: Pangea API token.
|
50
|
+
config: Configuration.
|
51
|
+
logger_name: Logger name.
|
52
|
+
|
53
|
+
Examples:
|
54
|
+
config = PangeaConfig(domain="pangea_domain")
|
55
|
+
redact = RedactAsync(token="pangea_token", config=config)
|
56
|
+
"""
|
57
|
+
|
39
58
|
super().__init__(token, config, logger_name, config_id=config_id)
|
40
59
|
|
41
60
|
async def redact(
|
@@ -45,6 +64,7 @@ class RedactAsync(ServiceBaseAsync):
|
|
45
64
|
rules: Optional[List[str]] = None,
|
46
65
|
rulesets: Optional[List[str]] = None,
|
47
66
|
return_result: Optional[bool] = None,
|
67
|
+
redaction_method_overrides: Optional[m.RedactionMethodOverrides] = None,
|
48
68
|
) -> PangeaResponse[m.RedactResult]:
|
49
69
|
"""
|
50
70
|
Redact
|
@@ -60,6 +80,7 @@ class RedactAsync(ServiceBaseAsync):
|
|
60
80
|
rules (list[str], optional): An array of redact rule short names
|
61
81
|
rulesets (list[str], optional): An array of redact rulesets short names
|
62
82
|
return_result(bool, optional): Setting this value to false will omit the redacted result only returning count
|
83
|
+
redaction_method_overrides: A set of redaction method overrides for any enabled rule. These methods override the config declared methods
|
63
84
|
|
64
85
|
Raises:
|
65
86
|
PangeaAPIException: If an API Error happens
|
@@ -73,7 +94,14 @@ class RedactAsync(ServiceBaseAsync):
|
|
73
94
|
response = redact.redact(text="Jenny Jenny... 555-867-5309")
|
74
95
|
"""
|
75
96
|
|
76
|
-
input = m.RedactRequest(
|
97
|
+
input = m.RedactRequest(
|
98
|
+
text=text,
|
99
|
+
debug=debug,
|
100
|
+
rules=rules,
|
101
|
+
rulesets=rulesets,
|
102
|
+
return_result=return_result,
|
103
|
+
redaction_method_overrides=redaction_method_overrides,
|
104
|
+
)
|
77
105
|
return await self.request.post("v1/redact", m.RedactResult, data=input.dict(exclude_none=True))
|
78
106
|
|
79
107
|
async def redact_structured(
|
@@ -85,6 +113,7 @@ class RedactAsync(ServiceBaseAsync):
|
|
85
113
|
rules: Optional[List[str]] = None,
|
86
114
|
rulesets: Optional[List[str]] = None,
|
87
115
|
return_result: Optional[bool] = None,
|
116
|
+
redaction_method_overrides: Optional[m.RedactionMethodOverrides] = None,
|
88
117
|
) -> PangeaResponse[m.StructuredResult]:
|
89
118
|
"""
|
90
119
|
Redact structured
|
@@ -104,6 +133,7 @@ class RedactAsync(ServiceBaseAsync):
|
|
104
133
|
rules (list[str], optional): An array of redact rule short names
|
105
134
|
rulesets (list[str], optional): An array of redact rulesets short names
|
106
135
|
return_result(bool, optional): Setting this value to false will omit the redacted result only returning count
|
136
|
+
redaction_method_overrides: A set of redaction method overrides for any enabled rule. These methods override the config declared methods
|
107
137
|
|
108
138
|
Raises:
|
109
139
|
PangeaAPIException: If an API Error happens
|
@@ -130,5 +160,29 @@ class RedactAsync(ServiceBaseAsync):
|
|
130
160
|
rules=rules,
|
131
161
|
rulesets=rulesets,
|
132
162
|
return_result=return_result,
|
163
|
+
redaction_method_overrides=redaction_method_overrides,
|
133
164
|
)
|
134
165
|
return await self.request.post("v1/redact_structured", m.StructuredResult, data=input.dict(exclude_none=True))
|
166
|
+
|
167
|
+
async def unredact(self, redacted_data: m.RedactedData, fpe_context: str) -> PangeaResponse[m.UnredactResult]:
|
168
|
+
"""
|
169
|
+
Unredact
|
170
|
+
|
171
|
+
Decrypt or unredact fpe redactions
|
172
|
+
|
173
|
+
OperationId: redact_post_v1_unredact
|
174
|
+
|
175
|
+
Args:
|
176
|
+
redacted_data: Data to unredact
|
177
|
+
fpe_context (base64): FPE context used to decrypt and unredact data
|
178
|
+
|
179
|
+
Raises:
|
180
|
+
PangeaAPIException: If an API Error happens
|
181
|
+
|
182
|
+
Returns:
|
183
|
+
Pangea Response with redacted data in the response.result field,
|
184
|
+
available response fields can be found in our
|
185
|
+
[API Documentation](https://pangea.cloud/docs/api/redact#unredact)
|
186
|
+
"""
|
187
|
+
input = m.UnredactRequest(redacted_data=redacted_data, fpe_context=fpe_context)
|
188
|
+
return await self.request.post("v1/unredact", m.UnredactResult, data=input.dict(exclude_none=True))
|
pangea/asyncio/services/vault.py
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
+
from __future__ import annotations
|
4
|
+
|
3
5
|
import datetime
|
4
6
|
from typing import Dict, Optional, Union
|
5
7
|
|
6
8
|
from pangea.asyncio.services.base import ServiceBaseAsync
|
9
|
+
from pangea.config import PangeaConfig
|
7
10
|
from pangea.response import PangeaResponse
|
8
11
|
from pangea.services.vault.models.asymmetric import (
|
9
12
|
AsymmetricGenerateRequest,
|
@@ -17,6 +20,8 @@ from pangea.services.vault.models.asymmetric import (
|
|
17
20
|
)
|
18
21
|
from pangea.services.vault.models.common import (
|
19
22
|
AsymmetricAlgorithm,
|
23
|
+
DecryptTransformRequest,
|
24
|
+
DecryptTransformResult,
|
20
25
|
DeleteRequest,
|
21
26
|
DeleteResult,
|
22
27
|
EncodedPrivateKey,
|
@@ -24,6 +29,8 @@ from pangea.services.vault.models.common import (
|
|
24
29
|
EncodedSymmetricKey,
|
25
30
|
EncryptStructuredRequest,
|
26
31
|
EncryptStructuredResult,
|
32
|
+
EncryptTransformRequest,
|
33
|
+
EncryptTransformResult,
|
27
34
|
FolderCreateRequest,
|
28
35
|
FolderCreateResult,
|
29
36
|
GetRequest,
|
@@ -50,6 +57,7 @@ from pangea.services.vault.models.common import (
|
|
50
57
|
SymmetricAlgorithm,
|
51
58
|
Tags,
|
52
59
|
TDict,
|
60
|
+
TransformAlphabet,
|
53
61
|
UpdateRequest,
|
54
62
|
UpdateResult,
|
55
63
|
)
|
@@ -98,10 +106,24 @@ class VaultAsync(ServiceBaseAsync):
|
|
98
106
|
|
99
107
|
def __init__(
|
100
108
|
self,
|
101
|
-
token,
|
102
|
-
config=None,
|
103
|
-
logger_name="pangea",
|
104
|
-
):
|
109
|
+
token: str,
|
110
|
+
config: PangeaConfig | None = None,
|
111
|
+
logger_name: str = "pangea",
|
112
|
+
) -> None:
|
113
|
+
"""
|
114
|
+
Vault client
|
115
|
+
|
116
|
+
Initializes a new Vault client.
|
117
|
+
|
118
|
+
Args:
|
119
|
+
token: Pangea API token.
|
120
|
+
config: Configuration.
|
121
|
+
logger_name: Logger name.
|
122
|
+
|
123
|
+
Examples:
|
124
|
+
config = PangeaConfig(domain="pangea_domain")
|
125
|
+
vault = VaultAsync(token="pangea_token", config=config)
|
126
|
+
"""
|
105
127
|
super().__init__(token, config, logger_name)
|
106
128
|
|
107
129
|
# Delete endpoint
|
@@ -1278,3 +1300,92 @@ class VaultAsync(ServiceBaseAsync):
|
|
1278
1300
|
EncryptStructuredResult,
|
1279
1301
|
data=input.dict(exclude_none=True),
|
1280
1302
|
)
|
1303
|
+
|
1304
|
+
async def encrypt_transform(
|
1305
|
+
self,
|
1306
|
+
id: str,
|
1307
|
+
plain_text: str,
|
1308
|
+
alphabet: TransformAlphabet,
|
1309
|
+
tweak: str | None = None,
|
1310
|
+
version: int | None = None,
|
1311
|
+
) -> PangeaResponse[EncryptTransformResult]:
|
1312
|
+
"""
|
1313
|
+
Encrypt transform
|
1314
|
+
|
1315
|
+
Encrypt using a format-preserving algorithm (FPE).
|
1316
|
+
|
1317
|
+
OperationId: vault_post_v1_key_encrypt_transform
|
1318
|
+
|
1319
|
+
Args:
|
1320
|
+
id: The item ID.
|
1321
|
+
plain_text: A message to be encrypted.
|
1322
|
+
alphabet: Set of characters to use for format-preserving encryption (FPE).
|
1323
|
+
tweak: User provided tweak string. If not provided, a random string will be generated and returned.
|
1324
|
+
version: The item version. Defaults to the current version.
|
1325
|
+
|
1326
|
+
Raises:
|
1327
|
+
PangeaAPIException: If an API error happens.
|
1328
|
+
|
1329
|
+
Returns:
|
1330
|
+
A `PangeaResponse` containing the encrypted message.
|
1331
|
+
|
1332
|
+
Examples:
|
1333
|
+
await vault.encrypt_transform(
|
1334
|
+
id="pvi_[...]",
|
1335
|
+
plain_text="message to encrypt",
|
1336
|
+
alphabet=TransformAlphabet.ALPHANUMERIC,
|
1337
|
+
tweak="MTIzMTIzMT==",
|
1338
|
+
)
|
1339
|
+
"""
|
1340
|
+
|
1341
|
+
input = EncryptTransformRequest(
|
1342
|
+
id=id,
|
1343
|
+
plain_text=plain_text,
|
1344
|
+
alphabet=alphabet,
|
1345
|
+
tweak=tweak,
|
1346
|
+
version=version,
|
1347
|
+
)
|
1348
|
+
return await self.request.post(
|
1349
|
+
"v1/key/encrypt/transform",
|
1350
|
+
EncryptTransformResult,
|
1351
|
+
data=input.dict(exclude_none=True),
|
1352
|
+
)
|
1353
|
+
|
1354
|
+
async def decrypt_transform(
|
1355
|
+
self, id: str, cipher_text: str, tweak: str, alphabet: TransformAlphabet, version: int | None = None
|
1356
|
+
) -> PangeaResponse[DecryptTransformResult]:
|
1357
|
+
"""
|
1358
|
+
Decrypt transform
|
1359
|
+
|
1360
|
+
Decrypt using a format-preserving algorithm (FPE).
|
1361
|
+
|
1362
|
+
OperationId: vault_post_v1_key_decrypt_transform
|
1363
|
+
|
1364
|
+
Args:
|
1365
|
+
id: The item ID.
|
1366
|
+
cipher_text: A message encrypted by Vault.
|
1367
|
+
tweak: User provided tweak string.
|
1368
|
+
alphabet: Set of characters to use for format-preserving encryption (FPE).
|
1369
|
+
version: The item version. Defaults to the current version.
|
1370
|
+
|
1371
|
+
Raises:
|
1372
|
+
PangeaAPIException: If an API error happens.
|
1373
|
+
|
1374
|
+
Returns:
|
1375
|
+
A `PangeaResponse` containing the decrypted message.
|
1376
|
+
|
1377
|
+
Examples:
|
1378
|
+
await vault.decrypt_transform(
|
1379
|
+
id="pvi_[...]",
|
1380
|
+
cipher_text="encrypted message",
|
1381
|
+
tweak="MTIzMTIzMT==",
|
1382
|
+
alphabet=TransformAlphabet.ALPHANUMERIC,
|
1383
|
+
)
|
1384
|
+
"""
|
1385
|
+
|
1386
|
+
input = DecryptTransformRequest(id=id, cipher_text=cipher_text, tweak=tweak, alphabet=alphabet, version=version)
|
1387
|
+
return await self.request.post(
|
1388
|
+
"v1/key/decrypt/transform",
|
1389
|
+
DecryptTransformResult,
|
1390
|
+
data=input.dict(exclude_none=True),
|
1391
|
+
)
|
pangea/request.py
CHANGED
@@ -5,12 +5,12 @@ import copy
|
|
5
5
|
import json
|
6
6
|
import logging
|
7
7
|
import time
|
8
|
-
from typing import Dict, List, Optional, Tuple, Type, Union
|
8
|
+
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type, Union
|
9
9
|
|
10
|
-
import aiohttp
|
11
10
|
import requests
|
12
11
|
from requests.adapters import HTTPAdapter, Retry
|
13
12
|
from requests_toolbelt import MultipartDecoder # type: ignore
|
13
|
+
from typing_extensions import TypeVar
|
14
14
|
|
15
15
|
import pangea
|
16
16
|
import pangea.exceptions as pe
|
@@ -18,6 +18,9 @@ from pangea.config import PangeaConfig
|
|
18
18
|
from pangea.response import AttachedFile, PangeaResponse, PangeaResponseResult, ResponseStatus, TransferMethod
|
19
19
|
from pangea.utils import default_encoder
|
20
20
|
|
21
|
+
if TYPE_CHECKING:
|
22
|
+
import aiohttp
|
23
|
+
|
21
24
|
|
22
25
|
class MultipartResponse(object):
|
23
26
|
pangea_json: Dict[str, str]
|
@@ -187,6 +190,9 @@ class PangeaRequestBase(object):
|
|
187
190
|
raise pe.PangeaAPIException(f"{summary} ", response)
|
188
191
|
|
189
192
|
|
193
|
+
TResult = TypeVar("TResult", bound=PangeaResponseResult, default=PangeaResponseResult)
|
194
|
+
|
195
|
+
|
190
196
|
class PangeaRequest(PangeaRequestBase):
|
191
197
|
"""An object that makes direct calls to Pangea Service APIs.
|
192
198
|
|
@@ -202,12 +208,12 @@ class PangeaRequest(PangeaRequestBase):
|
|
202
208
|
def post(
|
203
209
|
self,
|
204
210
|
endpoint: str,
|
205
|
-
result_class: Type[
|
211
|
+
result_class: Type[TResult],
|
206
212
|
data: Union[str, Dict] = {},
|
207
213
|
files: Optional[List[Tuple]] = None,
|
208
214
|
poll_result: bool = True,
|
209
215
|
url: Optional[str] = None,
|
210
|
-
) -> PangeaResponse:
|
216
|
+
) -> PangeaResponse[TResult]:
|
211
217
|
"""Makes the POST call to a Pangea Service endpoint.
|
212
218
|
|
213
219
|
Args:
|
@@ -343,7 +349,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
343
349
|
|
344
350
|
return data, files
|
345
351
|
|
346
|
-
def _handle_queued_result(self, response: PangeaResponse) -> PangeaResponse[
|
352
|
+
def _handle_queued_result(self, response: PangeaResponse[TResult]) -> PangeaResponse[TResult]:
|
347
353
|
if self._queued_retry_enabled and response.http_status == 202:
|
348
354
|
self.logger.debug(
|
349
355
|
json.dumps(
|
@@ -355,7 +361,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
355
361
|
|
356
362
|
return response
|
357
363
|
|
358
|
-
def get(self, path: str, result_class: Type[
|
364
|
+
def get(self, path: str, result_class: Type[TResult], check_response: bool = True) -> PangeaResponse[TResult]:
|
359
365
|
"""Makes the GET call to a Pangea Service endpoint.
|
360
366
|
|
361
367
|
Args:
|
@@ -427,21 +433,21 @@ class PangeaRequest(PangeaRequestBase):
|
|
427
433
|
raise pe.DownloadFileError(f"Failed to download file. Status: {response.status_code}", response.text)
|
428
434
|
|
429
435
|
def poll_result_by_id(
|
430
|
-
self, request_id: str, result_class:
|
431
|
-
):
|
436
|
+
self, request_id: str, result_class: Type[TResult], check_response: bool = True
|
437
|
+
) -> PangeaResponse[TResult]:
|
432
438
|
path = self._get_poll_path(request_id)
|
433
439
|
self.logger.debug(json.dumps({"service": self.service, "action": "poll_result_once", "url": path}))
|
434
|
-
return self.get(path, result_class, check_response=check_response)
|
440
|
+
return self.get(path, result_class, check_response=check_response)
|
435
441
|
|
436
442
|
def poll_result_once(
|
437
|
-
self, response: PangeaResponse, check_response: bool = True
|
438
|
-
) -> PangeaResponse[
|
443
|
+
self, response: PangeaResponse[TResult], check_response: bool = True
|
444
|
+
) -> PangeaResponse[TResult]:
|
439
445
|
request_id = response.request_id
|
440
446
|
if not request_id:
|
441
447
|
raise pe.PangeaException("Poll result error: response did not include a 'request_id'")
|
442
448
|
|
443
449
|
if response.status != ResponseStatus.ACCEPTED.value:
|
444
|
-
raise pe.PangeaException("Response already
|
450
|
+
raise pe.PangeaException("Response already processed")
|
445
451
|
|
446
452
|
return self.poll_result_by_id(request_id, response.result_class, check_response=check_response)
|
447
453
|
|
@@ -453,8 +459,10 @@ class PangeaRequest(PangeaRequestBase):
|
|
453
459
|
) -> PangeaResponse:
|
454
460
|
# Send request
|
455
461
|
try:
|
456
|
-
# This should return 202 (AcceptedRequestException)
|
457
|
-
|
462
|
+
# This should return 202 (AcceptedRequestException)
|
463
|
+
resp = self.post(endpoint=endpoint, result_class=result_class, data=data, poll_result=False)
|
464
|
+
raise pe.PresignedURLException("Should return 202", resp)
|
465
|
+
|
458
466
|
except pe.AcceptedRequestException as e:
|
459
467
|
accepted_exception = e
|
460
468
|
except Exception as e:
|
@@ -512,9 +520,6 @@ class PangeaRequest(PangeaRequestBase):
|
|
512
520
|
raise AttributeError("files attribute should have at least 1 file")
|
513
521
|
|
514
522
|
response = self.request_presigned_url(endpoint=endpoint, result_class=result_class, data=data)
|
515
|
-
|
516
|
-
if response.success: # This should only happen when uploading a zero bytes file
|
517
|
-
return response.raw_response
|
518
523
|
if response.accepted_result is None:
|
519
524
|
raise pe.PangeaException("No accepted_result field when requesting presigned url")
|
520
525
|
if response.accepted_result.post_url is None:
|
@@ -526,7 +531,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
526
531
|
self.post_presigned_url(url=presigned_url, data=data_to_presigned, files=files)
|
527
532
|
return response.raw_response
|
528
533
|
|
529
|
-
def _poll_result_retry(self, response: PangeaResponse) -> PangeaResponse[
|
534
|
+
def _poll_result_retry(self, response: PangeaResponse[TResult]) -> PangeaResponse[TResult]:
|
530
535
|
retry_count = 1
|
531
536
|
start = time.time()
|
532
537
|
|
@@ -538,9 +543,7 @@ class PangeaRequest(PangeaRequestBase):
|
|
538
543
|
self.logger.debug(json.dumps({"service": self.service, "action": "poll_result_retry", "step": "exit"}))
|
539
544
|
return self._check_response(response)
|
540
545
|
|
541
|
-
def _poll_presigned_url(
|
542
|
-
self, response: PangeaResponse[Type[PangeaResponseResult]]
|
543
|
-
) -> PangeaResponse[Type[PangeaResponseResult]]:
|
546
|
+
def _poll_presigned_url(self, response: PangeaResponse[TResult]) -> PangeaResponse[TResult]:
|
544
547
|
if response.http_status != 202:
|
545
548
|
raise AttributeError("Response should be 202")
|
546
549
|
|
pangea/response.py
CHANGED
@@ -3,16 +3,15 @@
|
|
3
3
|
import datetime
|
4
4
|
import enum
|
5
5
|
import os
|
6
|
-
from typing import Any, Dict, Generic, List, Optional, Type,
|
6
|
+
from typing import Any, Dict, Generic, List, Optional, Type, Union
|
7
7
|
|
8
8
|
import aiohttp
|
9
9
|
import requests
|
10
10
|
from pydantic import BaseModel
|
11
|
+
from typing_extensions import TypeVar
|
11
12
|
|
12
13
|
from pangea.utils import format_datetime
|
13
14
|
|
14
|
-
T = TypeVar("T")
|
15
|
-
|
16
15
|
|
17
16
|
class AttachedFile(object):
|
18
17
|
filename: str
|
@@ -29,7 +28,6 @@ class AttachedFile(object):
|
|
29
28
|
filename = self.filename if self.filename else "default_save_filename"
|
30
29
|
|
31
30
|
filepath = os.path.join(dest_folder, filename)
|
32
|
-
filepath = self._find_available_file(filepath)
|
33
31
|
directory = os.path.dirname(filepath)
|
34
32
|
if not os.path.exists(directory):
|
35
33
|
os.makedirs(directory)
|
@@ -37,17 +35,6 @@ class AttachedFile(object):
|
|
37
35
|
with open(filepath, "wb") as file:
|
38
36
|
file.write(self.file)
|
39
37
|
|
40
|
-
def _find_available_file(self, file_path):
|
41
|
-
base_name, ext = os.path.splitext(file_path)
|
42
|
-
counter = 1
|
43
|
-
while os.path.exists(file_path):
|
44
|
-
if ext:
|
45
|
-
file_path = f"{base_name}_{counter}{ext}"
|
46
|
-
else:
|
47
|
-
file_path = f"{base_name}_{counter}"
|
48
|
-
counter += 1
|
49
|
-
return file_path
|
50
|
-
|
51
38
|
|
52
39
|
class TransferMethod(str, enum.Enum):
|
53
40
|
MULTIPART = "multipart"
|
@@ -155,22 +142,34 @@ class ResponseStatus(str, enum.Enum):
|
|
155
142
|
|
156
143
|
|
157
144
|
class ResponseHeader(APIResponseModel):
|
158
|
-
"""
|
159
|
-
Pangea response API header.
|
160
|
-
|
161
|
-
Arguments:
|
162
|
-
request_id -- The request ID.
|
163
|
-
request_time -- The time the request was issued, ISO8601.
|
164
|
-
response_time -- The time the response was issued, ISO8601.
|
165
|
-
status -- Pangea response status
|
166
|
-
summary -- The summary of the response.
|
167
|
-
"""
|
145
|
+
"""Pangea response API header."""
|
168
146
|
|
169
147
|
request_id: str
|
148
|
+
"""A unique identifier assigned to each request made to the API."""
|
149
|
+
|
170
150
|
request_time: str
|
151
|
+
"""
|
152
|
+
Timestamp indicating the exact moment when a request is made to the API.
|
153
|
+
"""
|
154
|
+
|
171
155
|
response_time: str
|
156
|
+
"""
|
157
|
+
Duration it takes for the API to process a request and generate a response.
|
158
|
+
"""
|
159
|
+
|
172
160
|
status: str
|
161
|
+
"""
|
162
|
+
Represents the status or outcome of the API request.
|
163
|
+
"""
|
164
|
+
|
173
165
|
summary: str
|
166
|
+
"""
|
167
|
+
Provides a concise and brief overview of the purpose or primary objective of
|
168
|
+
the API endpoint.
|
169
|
+
"""
|
170
|
+
|
171
|
+
|
172
|
+
T = TypeVar("T", bound=PangeaResponseResult, default=PangeaResponseResult)
|
174
173
|
|
175
174
|
|
176
175
|
class PangeaResponse(Generic[T], ResponseHeader):
|
@@ -179,14 +178,14 @@ class PangeaResponse(Generic[T], ResponseHeader):
|
|
179
178
|
result: Optional[T] = None
|
180
179
|
pangea_error: Optional[PangeaError] = None
|
181
180
|
accepted_result: Optional[AcceptedResult] = None
|
182
|
-
result_class:
|
181
|
+
result_class: Type[T] = PangeaResponseResult # type: ignore[assignment]
|
183
182
|
_json: Any
|
184
183
|
attached_files: List[AttachedFile] = []
|
185
184
|
|
186
185
|
def __init__(
|
187
186
|
self,
|
188
187
|
response: requests.Response,
|
189
|
-
result_class:
|
188
|
+
result_class: Type[T],
|
190
189
|
json: dict,
|
191
190
|
attached_files: List[AttachedFile] = [],
|
192
191
|
):
|
@@ -198,7 +197,7 @@ class PangeaResponse(Generic[T], ResponseHeader):
|
|
198
197
|
self.attached_files = attached_files
|
199
198
|
|
200
199
|
self.result = (
|
201
|
-
self.result_class(**self.raw_result)
|
200
|
+
self.result_class(**self.raw_result)
|
202
201
|
if self.raw_result is not None and issubclass(self.result_class, PangeaResponseResult) and self.success
|
203
202
|
else None
|
204
203
|
)
|
pangea/services/__init__.py
CHANGED