qmenta-core 4.1.dev709__tar.gz → 4.1.dev711__tar.gz
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.
Potentially problematic release.
This version of qmenta-core might be problematic. Click here for more details.
- {qmenta_core-4.1.dev709 → qmenta_core-4.1.dev711}/PKG-INFO +3 -2
- {qmenta_core-4.1.dev709 → qmenta_core-4.1.dev711}/pyproject.toml +4 -3
- {qmenta_core-4.1.dev709 → qmenta_core-4.1.dev711}/src/qmenta/core/auth.py +7 -6
- {qmenta_core-4.1.dev709 → qmenta_core-4.1.dev711}/src/qmenta/core/platform.py +1 -1
- {qmenta_core-4.1.dev709 → qmenta_core-4.1.dev711}/src/qmenta/core/upload/single.py +34 -17
- {qmenta_core-4.1.dev709 → qmenta_core-4.1.dev711}/README.md +0 -0
- {qmenta_core-4.1.dev709 → qmenta_core-4.1.dev711}/src/qmenta/__init__.py +0 -0
- {qmenta_core-4.1.dev709 → qmenta_core-4.1.dev711}/src/qmenta/core/.gitignore +0 -0
- {qmenta_core-4.1.dev709 → qmenta_core-4.1.dev711}/src/qmenta/core/__init__.py +0 -0
- {qmenta_core-4.1.dev709 → qmenta_core-4.1.dev711}/src/qmenta/core/errors.py +0 -0
- {qmenta_core-4.1.dev709 → qmenta_core-4.1.dev711}/src/qmenta/core/upload/__init__.py +0 -0
- {qmenta_core-4.1.dev709 → qmenta_core-4.1.dev711}/src/qmenta/core/upload/multi.py +0 -0
- {qmenta_core-4.1.dev709 → qmenta_core-4.1.dev711}/src/qmenta/core/upload/prepare.py +0 -0
- {qmenta_core-4.1.dev709 → qmenta_core-4.1.dev711}/src/qmenta/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: qmenta-core
|
|
3
|
-
Version: 4.1.
|
|
3
|
+
Version: 4.1.dev711
|
|
4
4
|
Summary: QMENTA core library to communicate with the QMENTA platform.
|
|
5
5
|
License: Proprietary
|
|
6
6
|
Author: QMENTA
|
|
@@ -16,9 +16,10 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
17
|
Requires-Dist: blinker (>=1.4,<2.0)
|
|
18
18
|
Requires-Dist: importlib-metadata (>=6.8.0,<7.0.0)
|
|
19
|
+
Requires-Dist: packaging (>=25.0,<26.0)
|
|
19
20
|
Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
|
|
20
21
|
Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
|
|
21
|
-
Requires-Dist: qmenta-anon (>=2.
|
|
22
|
+
Requires-Dist: qmenta-anon (>=2.1.dev377,<3.0)
|
|
22
23
|
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
23
24
|
Requires-Dist: xdg (>=6.0.0,<7.0.0)
|
|
24
25
|
Project-URL: Documentation, https://docs.qmenta.com/core/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "qmenta-core"
|
|
3
|
-
version = "4.1.
|
|
3
|
+
version = "4.1.dev711"
|
|
4
4
|
description = "QMENTA core library to communicate with the QMENTA platform."
|
|
5
5
|
license = "Proprietary"
|
|
6
6
|
authors = ["QMENTA <dev@qmenta.com>"]
|
|
@@ -22,20 +22,21 @@ qmenta-auth = 'qmenta.core.auth:main'
|
|
|
22
22
|
python = "^3.10"
|
|
23
23
|
requests = "^2.31.0"
|
|
24
24
|
pyyaml = "^6.0.1"
|
|
25
|
-
qmenta-anon = "^2.
|
|
25
|
+
qmenta-anon = "^2.1.dev377"
|
|
26
26
|
importlib-metadata = "^6.8.0"
|
|
27
27
|
xdg = "^6.0.0"
|
|
28
28
|
python-dotenv = "^1.0.0"
|
|
29
29
|
# We are not requiring the latest blinker 1.6.2 because that is not
|
|
30
30
|
# available in Google Colab, see EN-1810.
|
|
31
31
|
blinker = "^1.4"
|
|
32
|
+
packaging = "^25.0"
|
|
32
33
|
|
|
33
34
|
[tool.poetry.group.dev.dependencies]
|
|
34
35
|
flake8 = "^6.1.0"
|
|
35
36
|
mypy = "^1.5.1"
|
|
36
37
|
pytest = "^7.4.0"
|
|
37
38
|
coverage = {version = "^7.3.0", extras = ["toml"]}
|
|
38
|
-
|
|
39
|
+
pytest-cov = "^6.1.1"
|
|
39
40
|
sphinx-rtd-theme = "^1.3.0"
|
|
40
41
|
dom-toml = "^0.6.1"
|
|
41
42
|
scriv = {version = "^1.3.1", extras = ["toml"]}
|
|
@@ -35,11 +35,12 @@ class Needs2FAError(ActionFailedError):
|
|
|
35
35
|
|
|
36
36
|
@unique
|
|
37
37
|
class PlatformURL(Enum):
|
|
38
|
-
platform = 'platform.qmenta.com'
|
|
39
|
-
staging = 'staging.qmenta.com'
|
|
40
|
-
test = 'test.qmenta.com'
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
platform = 'https://platform.qmenta.com'
|
|
39
|
+
staging = 'https://staging.qmenta.com'
|
|
40
|
+
test = 'https://test.qmenta.com'
|
|
41
|
+
test_insecure = 'http://test.qmenta.com'
|
|
42
|
+
local_ip = "http://127.0.0.1:8080"
|
|
43
|
+
localhost = "http://localhost:8080"
|
|
43
44
|
|
|
44
45
|
|
|
45
46
|
class Auth:
|
|
@@ -287,7 +288,7 @@ def validate_url(url: str) -> None:
|
|
|
287
288
|
)
|
|
288
289
|
|
|
289
290
|
url_values = [p_url.value for p_url in PlatformURL]
|
|
290
|
-
if parsed_url.netloc not in url_values:
|
|
291
|
+
if parsed_url.scheme + '://' + parsed_url.netloc not in url_values:
|
|
291
292
|
raise ValueError(
|
|
292
293
|
"base_url must be one of '{}', ".format("', '".join(url_values))
|
|
293
294
|
+ f"not '{parsed_url.netloc}'."
|
|
@@ -138,7 +138,7 @@ def parse_response(response: requests.Response) -> Any:
|
|
|
138
138
|
def post(
|
|
139
139
|
auth: Auth, endpoint: str, data: Dict[str, Any] = {},
|
|
140
140
|
headers: Dict[str, Any] = {}, stream: bool = False,
|
|
141
|
-
timeout: float =
|
|
141
|
+
timeout: float = 60.0
|
|
142
142
|
) -> requests.Response:
|
|
143
143
|
"""
|
|
144
144
|
Post the given data and headers to the specified platform's endpoint.
|
|
@@ -95,6 +95,11 @@ class FileInfo:
|
|
|
95
95
|
if self.is_result and not self.input_data_type:
|
|
96
96
|
self.input_data_type = "offline_analysis:1.0"
|
|
97
97
|
|
|
98
|
+
if self.input_data_type and ":" not in self.input_data_type:
|
|
99
|
+
raise ValueError(
|
|
100
|
+
"If input_data_type is provided it must include its version"
|
|
101
|
+
)
|
|
102
|
+
|
|
98
103
|
if self.split_data and self.session_id:
|
|
99
104
|
# Don't split data when session_id is set
|
|
100
105
|
self.split_data = False
|
|
@@ -148,6 +153,12 @@ class SingleUpload:
|
|
|
148
153
|
after the upload finished. When set to False, the ``qm-*.zip`` files
|
|
149
154
|
that were created by this SingleUpload will be deleted when the upload
|
|
150
155
|
if finished. Default value: True.
|
|
156
|
+
chunk_size: int
|
|
157
|
+
The maximum size in bytes of each chunk of file to upload.
|
|
158
|
+
Default value: 2**24 == 16 MiB (optimized for GCS Bucket Storage)
|
|
159
|
+
max_retries: int
|
|
160
|
+
Maximum number of retries when uploading a file before raising
|
|
161
|
+
an error. Default value: 5
|
|
151
162
|
|
|
152
163
|
Attributes
|
|
153
164
|
----------
|
|
@@ -186,12 +197,15 @@ class SingleUpload:
|
|
|
186
197
|
anonymise: bool = True,
|
|
187
198
|
upload_index: int = -1,
|
|
188
199
|
keep_created_files: bool = True,
|
|
200
|
+
chunk_size: int = 2**24, # 16 MiB
|
|
201
|
+
max_retries: int = 5
|
|
189
202
|
) -> None:
|
|
190
203
|
self.auth = auth
|
|
191
204
|
self.path = path
|
|
192
205
|
self.file_info = file_info
|
|
193
206
|
self.upload_index = upload_index
|
|
194
207
|
self.keep_created_files = keep_created_files
|
|
208
|
+
self.max_retries = max_retries
|
|
195
209
|
self._created_files_list: List[str] = []
|
|
196
210
|
|
|
197
211
|
# file_size will be set in _check_type_and_size if the original file
|
|
@@ -206,7 +220,7 @@ class SingleUpload:
|
|
|
206
220
|
self.upload_filename: str
|
|
207
221
|
|
|
208
222
|
# Optimized for GCS Bucket Storage
|
|
209
|
-
self._chunk_size =
|
|
223
|
+
self._chunk_size = chunk_size
|
|
210
224
|
|
|
211
225
|
if os.path.isfile(self.path):
|
|
212
226
|
self.input_filename = self.path
|
|
@@ -472,6 +486,16 @@ class SingleUpload:
|
|
|
472
486
|
UploadError
|
|
473
487
|
platform.ConnectionError
|
|
474
488
|
"""
|
|
489
|
+
def iterate_over_file() -> Generator[Any, Any, Any]:
|
|
490
|
+
with open(self.upload_filename, "rb") as stream:
|
|
491
|
+
while True:
|
|
492
|
+
contents = stream.read(self._chunk_size)
|
|
493
|
+
if not contents:
|
|
494
|
+
break
|
|
495
|
+
else:
|
|
496
|
+
yield contents
|
|
497
|
+
self.bytes_uploaded += len(contents)
|
|
498
|
+
|
|
475
499
|
assert self.status == UploadStatus.TO_UPLOAD
|
|
476
500
|
self.status = UploadStatus.UPLOADING
|
|
477
501
|
self.file_size = prepare.get_zipfile_size(self.upload_filename)
|
|
@@ -495,7 +519,6 @@ class SingleUpload:
|
|
|
495
519
|
"_pid": self.file_info.project_id,
|
|
496
520
|
},
|
|
497
521
|
)
|
|
498
|
-
|
|
499
522
|
url_response_data = platform.parse_response(url_request_response)
|
|
500
523
|
signed_url: str = url_response_data["url"]
|
|
501
524
|
container_id: str = url_response_data["container_id"]
|
|
@@ -503,27 +526,14 @@ class SingleUpload:
|
|
|
503
526
|
md5_hash: str = self._file_md5(self.upload_filename)
|
|
504
527
|
|
|
505
528
|
# Stream file to signed-url without loading it to memory
|
|
506
|
-
max_retries: int = 5
|
|
507
529
|
completed_upload: bool = False
|
|
508
530
|
retries_count: int = 0
|
|
509
|
-
while not completed_upload and retries_count < max_retries:
|
|
510
|
-
|
|
511
|
-
def iterate_over_file() -> Generator[Any, Any, Any]:
|
|
512
|
-
with open(self.upload_filename, "rb") as stream:
|
|
513
|
-
while True:
|
|
514
|
-
contents = stream.read(self._chunk_size)
|
|
515
|
-
if not contents:
|
|
516
|
-
break
|
|
517
|
-
else:
|
|
518
|
-
yield contents
|
|
519
|
-
self.bytes_uploaded += len(contents)
|
|
520
|
-
|
|
531
|
+
while not completed_upload and retries_count < self.max_retries:
|
|
521
532
|
upload_response = post(
|
|
522
533
|
url=signed_url,
|
|
523
534
|
data=iterate_over_file(),
|
|
524
535
|
stream=True
|
|
525
536
|
)
|
|
526
|
-
|
|
527
537
|
# Verify upload
|
|
528
538
|
try:
|
|
529
539
|
self._validate_upload_response(
|
|
@@ -531,7 +541,7 @@ class SingleUpload:
|
|
|
531
541
|
)
|
|
532
542
|
except UploadError:
|
|
533
543
|
retries_count += 1
|
|
534
|
-
if retries_count < max_retries:
|
|
544
|
+
if retries_count < self.max_retries:
|
|
535
545
|
wait_time: int = 2 ** retries_count
|
|
536
546
|
time.sleep(wait_time)
|
|
537
547
|
print(
|
|
@@ -549,6 +559,11 @@ class SingleUpload:
|
|
|
549
559
|
)
|
|
550
560
|
)
|
|
551
561
|
|
|
562
|
+
tool_type = None
|
|
563
|
+
tool_version = None
|
|
564
|
+
if self.file_info.input_data_type:
|
|
565
|
+
tool_type, tool_version = self.file_info.input_data_type.split(":")
|
|
566
|
+
|
|
552
567
|
verification_response = platform.post(
|
|
553
568
|
auth=self.auth,
|
|
554
569
|
endpoint="file_manager/verify_upload_via_url",
|
|
@@ -556,6 +571,8 @@ class SingleUpload:
|
|
|
556
571
|
"container_id": container_id,
|
|
557
572
|
"path": fname,
|
|
558
573
|
"md5": md5_hash,
|
|
574
|
+
"tool_type": tool_type,
|
|
575
|
+
"tool_version": tool_version,
|
|
559
576
|
},
|
|
560
577
|
)
|
|
561
578
|
platform.parse_response(verification_response)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|