pangea-sdk 5.0.0__py3-none-any.whl → 5.2.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 -1
- pangea/asyncio/request.py +20 -4
- pangea/asyncio/services/__init__.py +1 -0
- pangea/asyncio/services/audit.py +6 -10
- pangea/asyncio/services/authz.py +23 -2
- pangea/asyncio/services/base.py +21 -2
- pangea/asyncio/services/intel.py +3 -0
- pangea/asyncio/services/sanitize.py +26 -2
- pangea/asyncio/services/share.py +643 -0
- pangea/deep_verify.py +7 -1
- pangea/dump_audit.py +8 -7
- pangea/request.py +20 -6
- pangea/response.py +12 -0
- pangea/services/__init__.py +1 -0
- pangea/services/audit/audit.py +5 -10
- pangea/services/authz.py +21 -1
- pangea/services/base.py +16 -2
- pangea/services/intel.py +18 -0
- pangea/services/redact.py +16 -0
- pangea/services/sanitize.py +22 -0
- pangea/services/share/file_format.py +170 -0
- pangea/services/share/share.py +1278 -0
- pangea/utils.py +88 -17
- {pangea_sdk-5.0.0.dist-info → pangea_sdk-5.2.0.dist-info}/METADATA +11 -10
- {pangea_sdk-5.0.0.dist-info → pangea_sdk-5.2.0.dist-info}/RECORD +26 -23
- {pangea_sdk-5.0.0.dist-info → pangea_sdk-5.2.0.dist-info}/WHEEL +0 -0
pangea/request.py
CHANGED
@@ -11,7 +11,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Tuple, Ty
|
|
11
11
|
import requests
|
12
12
|
from pydantic import BaseModel
|
13
13
|
from requests.adapters import HTTPAdapter, Retry
|
14
|
-
from requests_toolbelt import MultipartDecoder # type: ignore
|
14
|
+
from requests_toolbelt import MultipartDecoder # type: ignore[import-untyped]
|
15
15
|
from typing_extensions import TypeVar
|
16
16
|
|
17
17
|
import pangea
|
@@ -402,7 +402,20 @@ class PangeaRequest(PangeaRequestBase):
|
|
402
402
|
|
403
403
|
return self._check_response(pangea_response)
|
404
404
|
|
405
|
-
def download_file(self, url: str, filename:
|
405
|
+
def download_file(self, url: str, filename: str | None = None) -> AttachedFile:
|
406
|
+
"""
|
407
|
+
Download file
|
408
|
+
|
409
|
+
Download a file from the specified URL and save it with the given
|
410
|
+
filename.
|
411
|
+
|
412
|
+
Args:
|
413
|
+
url: URL of the file to download
|
414
|
+
filename: Name to save the downloaded file as. If not provided, the
|
415
|
+
filename will be determined from the Content-Disposition header or
|
416
|
+
the URL.
|
417
|
+
"""
|
418
|
+
|
406
419
|
self.logger.debug(
|
407
420
|
json.dumps(
|
408
421
|
{
|
@@ -467,10 +480,8 @@ class PangeaRequest(PangeaRequestBase):
|
|
467
480
|
) -> PangeaResponse:
|
468
481
|
# Send request
|
469
482
|
try:
|
470
|
-
# This should return 202 (AcceptedRequestException)
|
471
|
-
|
472
|
-
raise pe.PresignedURLException("Should return 202", resp)
|
473
|
-
|
483
|
+
# This should return 202 (AcceptedRequestException) at least zero size file is sent
|
484
|
+
return self.post(endpoint=endpoint, result_class=result_class, data=data, poll_result=False)
|
474
485
|
except pe.AcceptedRequestException as e:
|
475
486
|
accepted_exception = e
|
476
487
|
except Exception as e:
|
@@ -528,6 +539,9 @@ class PangeaRequest(PangeaRequestBase):
|
|
528
539
|
raise AttributeError("files attribute should have at least 1 file")
|
529
540
|
|
530
541
|
response = self.request_presigned_url(endpoint=endpoint, result_class=result_class, data=data)
|
542
|
+
|
543
|
+
if response.success: # This should only happen when uploading a zero bytes file
|
544
|
+
return response.raw_response
|
531
545
|
if response.accepted_result is None:
|
532
546
|
raise pe.PangeaException("No accepted_result field when requesting presigned url")
|
533
547
|
if response.accepted_result.post_url is None:
|
pangea/response.py
CHANGED
@@ -28,6 +28,7 @@ class AttachedFile(object):
|
|
28
28
|
filename = self.filename if self.filename else "default_save_filename"
|
29
29
|
|
30
30
|
filepath = os.path.join(dest_folder, filename)
|
31
|
+
filepath = self._find_available_file(filepath)
|
31
32
|
directory = os.path.dirname(filepath)
|
32
33
|
if not os.path.exists(directory):
|
33
34
|
os.makedirs(directory)
|
@@ -35,6 +36,17 @@ class AttachedFile(object):
|
|
35
36
|
with open(filepath, "wb") as file:
|
36
37
|
file.write(self.file)
|
37
38
|
|
39
|
+
def _find_available_file(self, file_path):
|
40
|
+
base_name, ext = os.path.splitext(file_path)
|
41
|
+
counter = 1
|
42
|
+
while os.path.exists(file_path):
|
43
|
+
if ext:
|
44
|
+
file_path = f"{base_name}_{counter}{ext}"
|
45
|
+
else:
|
46
|
+
file_path = f"{base_name}_{counter}"
|
47
|
+
counter += 1
|
48
|
+
return file_path
|
49
|
+
|
38
50
|
|
39
51
|
class TransferMethod(str, enum.Enum):
|
40
52
|
"""Transfer methods for uploading file data."""
|
pangea/services/__init__.py
CHANGED
pangea/services/audit/audit.py
CHANGED
@@ -492,7 +492,7 @@ class Audit(ServiceBase, AuditBase):
|
|
492
492
|
verbose: Optional[bool] = None,
|
493
493
|
) -> PangeaResponse[LogResult]:
|
494
494
|
"""
|
495
|
-
Log an
|
495
|
+
Log an event
|
496
496
|
|
497
497
|
Create a log entry in the Secure Audit Log.
|
498
498
|
|
@@ -501,6 +501,7 @@ class Audit(ServiceBase, AuditBase):
|
|
501
501
|
verify (bool, optional): True to verify logs consistency after response.
|
502
502
|
sign_local (bool, optional): True to sign event with local key.
|
503
503
|
verbose (bool, optional): True to get a more verbose response.
|
504
|
+
|
504
505
|
Raises:
|
505
506
|
AuditException: If an audit based api exception happens
|
506
507
|
PangeaAPIException: If an API Error happens
|
@@ -511,13 +512,7 @@ class Audit(ServiceBase, AuditBase):
|
|
511
512
|
Available response fields can be found in our [API documentation](https://pangea.cloud/docs/api/audit#/v1/log).
|
512
513
|
|
513
514
|
Examples:
|
514
|
-
|
515
|
-
log_response = audit.log({"message": "hello world"}, verbose=True)
|
516
|
-
print(f"Response. Hash: {log_response.result.hash}")
|
517
|
-
except pe.PangeaAPIException as e:
|
518
|
-
print(f"Request Error: {e.response.summary}")
|
519
|
-
for err in e.errors:
|
520
|
-
print(f"\\t{err.detail} \\n")
|
515
|
+
response = audit.log_event({"message": "hello world"}, verbose=True)
|
521
516
|
"""
|
522
517
|
|
523
518
|
input = self._get_log_request(event, sign_local=sign_local, verify=verify, verbose=verbose)
|
@@ -961,9 +956,9 @@ class Audit(ServiceBase, AuditBase):
|
|
961
956
|
if pub_root is not None:
|
962
957
|
self.pub_roots[tree_size] = pub_root
|
963
958
|
|
964
|
-
self.
|
959
|
+
self._fix_consistency_proofs(tree_sizes)
|
965
960
|
|
966
|
-
def
|
961
|
+
def _fix_consistency_proofs(self, tree_sizes: Iterable[int]) -> None:
|
967
962
|
# on very rare occasions, the consistency proof in Arweave may be wrong
|
968
963
|
# override it with the proof from pangea (not the root hash, just the proof)
|
969
964
|
for tree_size in tree_sizes:
|
pangea/services/authz.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
+
from __future__ import annotations
|
3
4
|
|
4
5
|
import enum
|
5
6
|
from typing import Any, Dict, List, Optional, Union
|
6
7
|
|
8
|
+
from pangea.config import PangeaConfig
|
7
9
|
from pangea.response import APIRequestModel, APIResponseModel, PangeaResponse, PangeaResponseResult
|
8
10
|
from pangea.services.base import ServiceBase
|
9
11
|
|
@@ -171,7 +173,25 @@ class AuthZ(ServiceBase):
|
|
171
173
|
|
172
174
|
service_name = "authz"
|
173
175
|
|
174
|
-
def __init__(
|
176
|
+
def __init__(
|
177
|
+
self, token: str, config: PangeaConfig | None = None, logger_name: str = "pangea", config_id: str | None = None
|
178
|
+
) -> None:
|
179
|
+
"""
|
180
|
+
AuthZ client
|
181
|
+
|
182
|
+
Initializes a new AuthZ client.
|
183
|
+
|
184
|
+
Args:
|
185
|
+
token: Pangea API token.
|
186
|
+
config: Configuration.
|
187
|
+
logger_name: Logger name.
|
188
|
+
config_id: Configuration ID.
|
189
|
+
|
190
|
+
Examples:
|
191
|
+
config = PangeaConfig(domain="aws.us.pangea.cloud")
|
192
|
+
authz = AuthZ(token="pangea_token", config=config)
|
193
|
+
"""
|
194
|
+
|
175
195
|
super().__init__(token, config, logger_name, config_id=config_id)
|
176
196
|
|
177
197
|
def tuple_create(self, tuples: List[Tuple]) -> PangeaResponse[TupleCreateResult]:
|
pangea/services/base.py
CHANGED
@@ -80,7 +80,8 @@ class ServiceBase(object):
|
|
80
80
|
Returns request's result that has been accepted by the server
|
81
81
|
|
82
82
|
Args:
|
83
|
-
exception
|
83
|
+
exception: Exception that was previously raised by the SDK on a call
|
84
|
+
that is being processed.
|
84
85
|
|
85
86
|
Returns:
|
86
87
|
PangeaResponse
|
@@ -100,5 +101,18 @@ class ServiceBase(object):
|
|
100
101
|
else:
|
101
102
|
raise AttributeError("Need to set exception, response or request_id")
|
102
103
|
|
103
|
-
def download_file(self, url: str, filename:
|
104
|
+
def download_file(self, url: str, filename: str | None = None) -> AttachedFile:
|
105
|
+
"""
|
106
|
+
Download file
|
107
|
+
|
108
|
+
Download a file from the specified URL and save it with the given
|
109
|
+
filename.
|
110
|
+
|
111
|
+
Args:
|
112
|
+
url: URL of the file to download
|
113
|
+
filename: Name to save the downloaded file as. If not provided, the
|
114
|
+
filename will be determined from the Content-Disposition header or
|
115
|
+
the URL.
|
116
|
+
"""
|
117
|
+
|
104
118
|
return self.request.download_file(url=url, filename=filename)
|
pangea/services/intel.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Copyright 2022 Pangea Cyber Corporation
|
2
2
|
# Author: Pangea Cyber Corporation
|
3
|
+
from __future__ import annotations
|
4
|
+
|
3
5
|
import enum
|
4
6
|
import hashlib
|
5
7
|
from typing import Dict, List, Optional
|
@@ -1237,6 +1239,7 @@ class UserBreachedRequest(IntelCommonRequest):
|
|
1237
1239
|
phone_number (str): A phone number to search for. minLength: 7, maxLength: 15.
|
1238
1240
|
start (str): Earliest date for search
|
1239
1241
|
end (str): Latest date for search
|
1242
|
+
cursor (str, optional): A token given in the raw response from SpyCloud. Post this back to paginate results
|
1240
1243
|
"""
|
1241
1244
|
|
1242
1245
|
email: Optional[str] = None
|
@@ -1245,6 +1248,7 @@ class UserBreachedRequest(IntelCommonRequest):
|
|
1245
1248
|
phone_number: Optional[str] = None
|
1246
1249
|
start: Optional[str] = None
|
1247
1250
|
end: Optional[str] = None
|
1251
|
+
cursor: Optional[str] = None
|
1248
1252
|
|
1249
1253
|
|
1250
1254
|
class UserBreachedBulkRequest(IntelCommonRequest):
|
@@ -1387,6 +1391,7 @@ class UserIntel(ServiceBase):
|
|
1387
1391
|
verbose: Optional[bool] = None,
|
1388
1392
|
raw: Optional[bool] = None,
|
1389
1393
|
provider: Optional[str] = None,
|
1394
|
+
cursor: Optional[str] = None,
|
1390
1395
|
) -> PangeaResponse[UserBreachedResult]:
|
1391
1396
|
"""
|
1392
1397
|
Look up breached users
|
@@ -1405,6 +1410,7 @@ class UserIntel(ServiceBase):
|
|
1405
1410
|
verbose (bool, optional): Echo the API parameters in the response
|
1406
1411
|
raw (bool, optional): Include raw data from this provider
|
1407
1412
|
provider (str, optional): Use reputation data from this provider: "spycloud"
|
1413
|
+
cursor (str, optional): A token given in the raw response from SpyCloud. Post this back to paginate results
|
1408
1414
|
|
1409
1415
|
Raises:
|
1410
1416
|
PangeaAPIException: If an API Error happens
|
@@ -1432,6 +1438,7 @@ class UserIntel(ServiceBase):
|
|
1432
1438
|
end=end,
|
1433
1439
|
verbose=verbose,
|
1434
1440
|
raw=raw,
|
1441
|
+
cursor=cursor,
|
1435
1442
|
)
|
1436
1443
|
return self.request.post("v1/user/breached", UserBreachedResult, data=input.model_dump(exclude_none=True))
|
1437
1444
|
|
@@ -1592,6 +1599,17 @@ class UserIntel(ServiceBase):
|
|
1592
1599
|
|
1593
1600
|
@staticmethod
|
1594
1601
|
def is_password_breached(response: PangeaResponse[UserBreachedResult], hash: str) -> PasswordStatus:
|
1602
|
+
"""
|
1603
|
+
Check if a password was breached
|
1604
|
+
|
1605
|
+
Helper function that simplifies searching the response's raw data for
|
1606
|
+
the full hash.
|
1607
|
+
|
1608
|
+
Args:
|
1609
|
+
response: API response from an earlier request
|
1610
|
+
hash: Password hash
|
1611
|
+
"""
|
1612
|
+
|
1595
1613
|
if response.result.raw_data is None: # type: ignore[union-attr]
|
1596
1614
|
raise PangeaException("Need raw data to check if hash is breached. Send request with raw=true")
|
1597
1615
|
|
pangea/services/redact.py
CHANGED
@@ -66,6 +66,18 @@ class RedactRequest(APIRequestModel):
|
|
66
66
|
rulesets: Optional[List[str]] = None
|
67
67
|
return_result: Optional[bool] = None
|
68
68
|
redaction_method_overrides: Optional[RedactionMethodOverrides] = None
|
69
|
+
vault_parameters: Optional[VaultParameters] = None
|
70
|
+
|
71
|
+
llm_request: Optional[bool] = None
|
72
|
+
"""Is this redact call going to be used in an LLM request?"""
|
73
|
+
|
74
|
+
|
75
|
+
class VaultParameters(APIRequestModel):
|
76
|
+
fpe_key_id: Optional[str] = None
|
77
|
+
"""A vault key ID of an exportable key used to redact with FPE instead of using the service config default."""
|
78
|
+
|
79
|
+
salt_secret_id: Optional[str] = None
|
80
|
+
"""A vault secret ID of a secret used to salt a hash instead of using the service config default."""
|
69
81
|
|
70
82
|
|
71
83
|
class RecognizerResult(APIResponseModel):
|
@@ -134,6 +146,10 @@ class StructuredRequest(APIRequestModel):
|
|
134
146
|
rulesets: Optional[List[str]] = None
|
135
147
|
return_result: Optional[bool] = None
|
136
148
|
redaction_method_overrides: Optional[RedactionMethodOverrides] = None
|
149
|
+
vault_parameters: Optional[VaultParameters] = None
|
150
|
+
|
151
|
+
llm_request: Optional[bool] = None
|
152
|
+
"""Is this redact call going to be used in an LLM request?"""
|
137
153
|
|
138
154
|
|
139
155
|
class StructuredResult(PangeaResponseResult):
|
pangea/services/sanitize.py
CHANGED
@@ -7,6 +7,7 @@ from typing import Dict, List, Optional, Tuple
|
|
7
7
|
|
8
8
|
from pydantic import Field
|
9
9
|
|
10
|
+
from pangea.config import PangeaConfig
|
10
11
|
from pangea.response import APIRequestModel, PangeaResponse, PangeaResponseResult, TransferMethod
|
11
12
|
from pangea.services.base import ServiceBase
|
12
13
|
from pangea.utils import FileUploadParams, get_file_upload_params
|
@@ -197,6 +198,27 @@ class Sanitize(ServiceBase):
|
|
197
198
|
|
198
199
|
service_name = "sanitize"
|
199
200
|
|
201
|
+
def __init__(
|
202
|
+
self, token: str, config: PangeaConfig | None = None, logger_name: str = "pangea", config_id: str | None = None
|
203
|
+
) -> None:
|
204
|
+
"""
|
205
|
+
Sanitize client
|
206
|
+
|
207
|
+
Initializes a new Sanitize client.
|
208
|
+
|
209
|
+
Args:
|
210
|
+
token: Pangea API token.
|
211
|
+
config: Configuration.
|
212
|
+
logger_name: Logger name.
|
213
|
+
config_id: Configuration ID.
|
214
|
+
|
215
|
+
Examples:
|
216
|
+
config = PangeaConfig(domain="aws.us.pangea.cloud")
|
217
|
+
authz = Sanitize(token="pangea_token", config=config)
|
218
|
+
"""
|
219
|
+
|
220
|
+
super().__init__(token, config, logger_name, config_id=config_id)
|
221
|
+
|
200
222
|
def sanitize(
|
201
223
|
self,
|
202
224
|
transfer_method: TransferMethod = TransferMethod.POST_URL,
|
@@ -0,0 +1,170 @@
|
|
1
|
+
import enum
|
2
|
+
|
3
|
+
|
4
|
+
class FileFormat(str, enum.Enum):
|
5
|
+
F3G2 = "3G2"
|
6
|
+
F3GP = "3GP"
|
7
|
+
F3MF = "3MF"
|
8
|
+
F7Z = "7Z"
|
9
|
+
A = "A"
|
10
|
+
AAC = "AAC"
|
11
|
+
ACCDB = "ACCDB"
|
12
|
+
AIFF = "AIFF"
|
13
|
+
AMF = "AMF"
|
14
|
+
AMR = "AMR"
|
15
|
+
APE = "APE"
|
16
|
+
ASF = "ASF"
|
17
|
+
ATOM = "ATOM"
|
18
|
+
AU = "AU"
|
19
|
+
AVI = "AVI"
|
20
|
+
AVIF = "AVIF"
|
21
|
+
BIN = "BIN"
|
22
|
+
BMP = "BMP"
|
23
|
+
BPG = "BPG"
|
24
|
+
BZ2 = "BZ2"
|
25
|
+
CAB = "CAB"
|
26
|
+
CLASS = "CLASS"
|
27
|
+
CPIO = "CPIO"
|
28
|
+
CRX = "CRX"
|
29
|
+
CSV = "CSV"
|
30
|
+
DAE = "DAE"
|
31
|
+
DBF = "DBF"
|
32
|
+
DCM = "DCM"
|
33
|
+
DEB = "DEB"
|
34
|
+
DJVU = "DJVU"
|
35
|
+
DLL = "DLL"
|
36
|
+
DOC = "DOC"
|
37
|
+
DOCX = "DOCX"
|
38
|
+
DWG = "DWG"
|
39
|
+
EOT = "EOT"
|
40
|
+
EPUB = "EPUB"
|
41
|
+
EXE = "EXE"
|
42
|
+
FDF = "FDF"
|
43
|
+
FITS = "FITS"
|
44
|
+
FLAC = "FLAC"
|
45
|
+
FLV = "FLV"
|
46
|
+
GBR = "GBR"
|
47
|
+
GEOJSON = "GEOJSON"
|
48
|
+
GIF = "GIF"
|
49
|
+
GLB = "GLB"
|
50
|
+
GML = "GML"
|
51
|
+
GPX = "GPX"
|
52
|
+
GZ = "GZ"
|
53
|
+
HAR = "HAR"
|
54
|
+
HDR = "HDR"
|
55
|
+
HEIC = "HEIC"
|
56
|
+
HEIF = "HEIF"
|
57
|
+
HTML = "HTML"
|
58
|
+
ICNS = "ICNS"
|
59
|
+
ICO = "ICO"
|
60
|
+
ICS = "ICS"
|
61
|
+
ISO = "ISO"
|
62
|
+
JAR = "JAR"
|
63
|
+
JP2 = "JP2"
|
64
|
+
JPF = "JPF"
|
65
|
+
JPG = "JPG"
|
66
|
+
JPM = "JPM"
|
67
|
+
JS = "JS"
|
68
|
+
JSON = "JSON"
|
69
|
+
JXL = "JXL"
|
70
|
+
JXR = "JXR"
|
71
|
+
KML = "KML"
|
72
|
+
LIT = "LIT"
|
73
|
+
LNK = "LNK"
|
74
|
+
LUA = "LUA"
|
75
|
+
LZ = "LZ"
|
76
|
+
M3U = "M3U"
|
77
|
+
M4A = "M4A"
|
78
|
+
MACHO = "MACHO"
|
79
|
+
MDB = "MDB"
|
80
|
+
MIDI = "MIDI"
|
81
|
+
MKV = "MKV"
|
82
|
+
MOBI = "MOBI"
|
83
|
+
MOV = "MOV"
|
84
|
+
MP3 = "MP3"
|
85
|
+
MP4 = "MP4"
|
86
|
+
MPC = "MPC"
|
87
|
+
MPEG = "MPEG"
|
88
|
+
MQV = "MQV"
|
89
|
+
MRC = "MRC"
|
90
|
+
MSG = "MSG"
|
91
|
+
MSI = "MSI"
|
92
|
+
NDJSON = "NDJSON"
|
93
|
+
NES = "NES"
|
94
|
+
ODC = "ODC"
|
95
|
+
ODF = "ODF"
|
96
|
+
ODG = "ODG"
|
97
|
+
ODP = "ODP"
|
98
|
+
ODS = "ODS"
|
99
|
+
ODT = "ODT"
|
100
|
+
OGA = "OGA"
|
101
|
+
OGV = "OGV"
|
102
|
+
OTF = "OTF"
|
103
|
+
OTG = "OTG"
|
104
|
+
OTP = "OTP"
|
105
|
+
OTS = "OTS"
|
106
|
+
OTT = "OTT"
|
107
|
+
OWL = "OWL"
|
108
|
+
P7S = "P7S"
|
109
|
+
PAT = "PAT"
|
110
|
+
PDF = "PDF"
|
111
|
+
PHP = "PHP"
|
112
|
+
PL = "PL"
|
113
|
+
PNG = "PNG"
|
114
|
+
PPT = "PPT"
|
115
|
+
PPTX = "PPTX"
|
116
|
+
PS = "PS"
|
117
|
+
PSD = "PSD"
|
118
|
+
PUB = "PUB"
|
119
|
+
PY = "PY"
|
120
|
+
QCP = "QCP"
|
121
|
+
RAR = "RAR"
|
122
|
+
RMVB = "RMVB"
|
123
|
+
RPM = "RPM"
|
124
|
+
RSS = "RSS"
|
125
|
+
RTF = "RTF"
|
126
|
+
SHP = "SHP"
|
127
|
+
SHX = "SHX"
|
128
|
+
SO = "SO"
|
129
|
+
SQLITE = "SQLITE"
|
130
|
+
SRT = "SRT"
|
131
|
+
SVG = "SVG"
|
132
|
+
SWF = "SWF"
|
133
|
+
SXC = "SXC"
|
134
|
+
TAR = "TAR"
|
135
|
+
TCL = "TCL"
|
136
|
+
TCX = "TCX"
|
137
|
+
TIFF = "TIFF"
|
138
|
+
TORRENT = "TORRENT"
|
139
|
+
TSV = "TSV"
|
140
|
+
TTC = "TTC"
|
141
|
+
TTF = "TTF"
|
142
|
+
TXT = "TXT"
|
143
|
+
VCF = "VCF"
|
144
|
+
VOC = "VOC"
|
145
|
+
VTT = "VTT"
|
146
|
+
WARC = "WARC"
|
147
|
+
WASM = "WASM"
|
148
|
+
WAV = "WAV"
|
149
|
+
WEBM = "WEBM"
|
150
|
+
WEBP = "WEBP"
|
151
|
+
WOFF = "WOFF"
|
152
|
+
WOFF2 = "WOFF2"
|
153
|
+
X3D = "X3D"
|
154
|
+
XAR = "XAR"
|
155
|
+
XCF = "XCF"
|
156
|
+
XFDF = "XFDF"
|
157
|
+
XLF = "XLF"
|
158
|
+
XLS = "XLS"
|
159
|
+
XLSX = "XLSX"
|
160
|
+
XML = "XML"
|
161
|
+
XPM = "XPM"
|
162
|
+
XZ = "XZ"
|
163
|
+
ZIP = "ZIP"
|
164
|
+
ZST = "ZST"
|
165
|
+
|
166
|
+
def __str__(self):
|
167
|
+
return str(self.value)
|
168
|
+
|
169
|
+
def __repr__(self):
|
170
|
+
return str(self.value)
|