figpack 0.1.7__py3-none-any.whl → 0.2.1__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.
Potentially problematic release.
This version of figpack might be problematic. Click here for more details.
- figpack/__init__.py +1 -1
- figpack/core/_upload_bundle.py +113 -73
- figpack/figpack-gui-dist/assets/{index-DaeClgi6.js → index-BqYF6BN-.js} +91 -91
- figpack/figpack-gui-dist/assets/index-Cmae55E4.css +1 -0
- figpack/figpack-gui-dist/index.html +2 -2
- {figpack-0.1.7.dist-info → figpack-0.2.1.dist-info}/METADATA +1 -1
- {figpack-0.1.7.dist-info → figpack-0.2.1.dist-info}/RECORD +11 -11
- figpack/figpack-gui-dist/assets/index-BDa2iJW9.css +0 -1
- {figpack-0.1.7.dist-info → figpack-0.2.1.dist-info}/WHEEL +0 -0
- {figpack-0.1.7.dist-info → figpack-0.2.1.dist-info}/entry_points.txt +0 -0
- {figpack-0.1.7.dist-info → figpack-0.2.1.dist-info}/licenses/LICENSE +0 -0
- {figpack-0.1.7.dist-info → figpack-0.2.1.dist-info}/top_level.txt +0 -0
figpack/__init__.py
CHANGED
figpack/core/_upload_bundle.py
CHANGED
|
@@ -16,23 +16,28 @@ from .config import FIGPACK_API_BASE_URL
|
|
|
16
16
|
thisdir = pathlib.Path(__file__).parent.resolve()
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def
|
|
20
|
-
figure_url: str, relative_path: str, file_path: pathlib.Path, api_key: str
|
|
21
|
-
) -> str:
|
|
19
|
+
def _get_batch_signed_urls(figure_url: str, files_batch: list, api_key: str) -> dict:
|
|
22
20
|
"""
|
|
23
|
-
|
|
21
|
+
Get signed URLs for a batch of files
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
figure_url: The figure URL
|
|
25
|
+
files_batch: List of tuples (relative_path, file_path)
|
|
26
|
+
api_key: API key for authentication
|
|
24
27
|
|
|
25
28
|
Returns:
|
|
26
|
-
|
|
29
|
+
dict: Mapping of relative_path to signed_url
|
|
27
30
|
"""
|
|
28
|
-
|
|
31
|
+
# Prepare batch request
|
|
32
|
+
files_data = []
|
|
33
|
+
for relative_path, file_path in files_batch:
|
|
34
|
+
file_size = file_path.stat().st_size
|
|
35
|
+
files_data.append({"relativePath": relative_path, "size": file_size})
|
|
29
36
|
|
|
30
|
-
# Get signed URL
|
|
31
37
|
payload = {
|
|
32
38
|
"figureUrl": figure_url,
|
|
33
|
-
"
|
|
39
|
+
"files": files_data,
|
|
34
40
|
"apiKey": api_key,
|
|
35
|
-
"size": file_size,
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
response = requests.post(f"{FIGPACK_API_BASE_URL}/api/upload", json=payload)
|
|
@@ -43,18 +48,35 @@ def _upload_single_file(
|
|
|
43
48
|
error_msg = error_data.get("message", "Unknown error")
|
|
44
49
|
except:
|
|
45
50
|
error_msg = f"HTTP {response.status_code}"
|
|
46
|
-
raise Exception(f"Failed to get signed
|
|
51
|
+
raise Exception(f"Failed to get signed URLs for batch: {error_msg}")
|
|
47
52
|
|
|
48
53
|
response_data = response.json()
|
|
49
54
|
if not response_data.get("success"):
|
|
50
55
|
raise Exception(
|
|
51
|
-
f"Failed to get signed
|
|
56
|
+
f"Failed to get signed URLs for batch: {response_data.get('message', 'Unknown error')}"
|
|
52
57
|
)
|
|
53
58
|
|
|
54
|
-
|
|
55
|
-
if not
|
|
56
|
-
raise Exception(
|
|
59
|
+
signed_urls_data = response_data.get("signedUrls", [])
|
|
60
|
+
if not signed_urls_data:
|
|
61
|
+
raise Exception("No signed URLs returned for batch")
|
|
62
|
+
|
|
63
|
+
# Convert to mapping
|
|
64
|
+
signed_urls_map = {}
|
|
65
|
+
for item in signed_urls_data:
|
|
66
|
+
signed_urls_map[item["relativePath"]] = item["signedUrl"]
|
|
67
|
+
|
|
68
|
+
return signed_urls_map
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _upload_single_file_with_signed_url(
|
|
72
|
+
relative_path: str, file_path: pathlib.Path, signed_url: str
|
|
73
|
+
) -> str:
|
|
74
|
+
"""
|
|
75
|
+
Upload a single file using a pre-obtained signed URL
|
|
57
76
|
|
|
77
|
+
Returns:
|
|
78
|
+
str: The relative path of the uploaded file
|
|
79
|
+
"""
|
|
58
80
|
# Upload file to signed URL
|
|
59
81
|
content_type = _determine_content_type(relative_path)
|
|
60
82
|
with open(file_path, "rb") as f:
|
|
@@ -235,39 +257,63 @@ def _upload_bundle(tmpdir: str, api_key: str, title: str = None) -> str:
|
|
|
235
257
|
print("No files to upload")
|
|
236
258
|
else:
|
|
237
259
|
print(
|
|
238
|
-
f"Uploading {total_files_to_upload} files with up to {MAX_WORKERS_FOR_UPLOAD} concurrent uploads..."
|
|
260
|
+
f"Uploading {total_files_to_upload} files in batches of 20 with up to {MAX_WORKERS_FOR_UPLOAD} concurrent uploads per batch..."
|
|
239
261
|
)
|
|
240
262
|
|
|
241
263
|
# Thread-safe progress tracking
|
|
242
264
|
uploaded_count = 0
|
|
243
265
|
count_lock = threading.Lock()
|
|
244
266
|
|
|
245
|
-
#
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
for
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
267
|
+
# Process files in batches of 20
|
|
268
|
+
batch_size = 20
|
|
269
|
+
for i in range(0, total_files_to_upload, batch_size):
|
|
270
|
+
batch = files_to_upload[i : i + batch_size]
|
|
271
|
+
batch_num = i // batch_size + 1
|
|
272
|
+
total_batches = (total_files_to_upload + batch_size - 1) // batch_size
|
|
273
|
+
|
|
274
|
+
print(
|
|
275
|
+
f"Processing batch {batch_num}/{total_batches} ({len(batch)} files)..."
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# Get signed URLs for this batch
|
|
279
|
+
try:
|
|
280
|
+
signed_urls_map = _get_batch_signed_urls(figure_url, batch, api_key)
|
|
281
|
+
except Exception as e:
|
|
282
|
+
print(f"Failed to get signed URLs for batch {batch_num}: {e}")
|
|
283
|
+
raise
|
|
284
|
+
|
|
285
|
+
# Upload files in this batch in parallel
|
|
286
|
+
with ThreadPoolExecutor(max_workers=MAX_WORKERS_FOR_UPLOAD) as executor:
|
|
287
|
+
# Submit upload tasks for this batch
|
|
288
|
+
future_to_file = {}
|
|
289
|
+
for rel_path, file_path in batch:
|
|
290
|
+
if rel_path in signed_urls_map:
|
|
291
|
+
future = executor.submit(
|
|
292
|
+
_upload_single_file_with_signed_url,
|
|
293
|
+
rel_path,
|
|
294
|
+
file_path,
|
|
295
|
+
signed_urls_map[rel_path],
|
|
266
296
|
)
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
297
|
+
future_to_file[future] = rel_path
|
|
298
|
+
else:
|
|
299
|
+
print(f"Warning: No signed URL found for {rel_path}")
|
|
300
|
+
|
|
301
|
+
# Process completed uploads for this batch
|
|
302
|
+
for future in as_completed(future_to_file):
|
|
303
|
+
relative_path = future_to_file[future]
|
|
304
|
+
try:
|
|
305
|
+
future.result() # This will raise any exception that occurred during upload
|
|
306
|
+
|
|
307
|
+
# Thread-safe progress update
|
|
308
|
+
with count_lock:
|
|
309
|
+
uploaded_count += 1
|
|
310
|
+
print(
|
|
311
|
+
f"Uploaded {uploaded_count}/{total_files_to_upload}: {relative_path}"
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
except Exception as e:
|
|
315
|
+
print(f"Failed to upload {relative_path}: {e}")
|
|
316
|
+
raise # Re-raise the exception to stop the upload process
|
|
271
317
|
|
|
272
318
|
# Create manifest for finalization
|
|
273
319
|
print("Creating manifest...")
|
|
@@ -285,49 +331,43 @@ def _upload_bundle(tmpdir: str, api_key: str, title: str = None) -> str:
|
|
|
285
331
|
|
|
286
332
|
print(f"Total size: {manifest['total_size'] / (1024 * 1024):.2f} MB")
|
|
287
333
|
|
|
288
|
-
# Upload manifest.json
|
|
334
|
+
# Upload manifest.json using batch API
|
|
289
335
|
print("Uploading manifest.json...")
|
|
290
336
|
manifest_content = json.dumps(manifest, indent=2)
|
|
291
337
|
manifest_size = len(manifest_content.encode("utf-8"))
|
|
292
338
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
"relativePath": "manifest.json",
|
|
296
|
-
"apiKey": api_key,
|
|
297
|
-
"size": manifest_size,
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
response = requests.post(
|
|
301
|
-
f"{FIGPACK_API_BASE_URL}/api/upload", json=manifest_payload
|
|
302
|
-
)
|
|
303
|
-
if not response.ok:
|
|
304
|
-
try:
|
|
305
|
-
error_data = response.json()
|
|
306
|
-
error_msg = error_data.get("message", "Unknown error")
|
|
307
|
-
except:
|
|
308
|
-
error_msg = f"HTTP {response.status_code}"
|
|
309
|
-
raise Exception(f"Failed to get signed URL for manifest.json: {error_msg}")
|
|
339
|
+
# Create a temporary file for the manifest
|
|
340
|
+
import tempfile
|
|
310
341
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
)
|
|
342
|
+
with tempfile.NamedTemporaryFile(
|
|
343
|
+
mode="w", suffix=".json", delete=False
|
|
344
|
+
) as temp_file:
|
|
345
|
+
temp_file.write(manifest_content)
|
|
346
|
+
temp_file_path = pathlib.Path(temp_file.name)
|
|
316
347
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
348
|
+
try:
|
|
349
|
+
# Use batch API for manifest
|
|
350
|
+
manifest_batch = [("manifest.json", temp_file_path)]
|
|
351
|
+
signed_urls_map = _get_batch_signed_urls(figure_url, manifest_batch, api_key)
|
|
320
352
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
signed_url, data=manifest_content, headers={"Content-Type": "application/json"}
|
|
324
|
-
)
|
|
353
|
+
if "manifest.json" not in signed_urls_map:
|
|
354
|
+
raise Exception("No signed URL returned for manifest.json")
|
|
325
355
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
356
|
+
# Upload manifest using signed URL
|
|
357
|
+
upload_response = requests.put(
|
|
358
|
+
signed_urls_map["manifest.json"],
|
|
359
|
+
data=manifest_content,
|
|
360
|
+
headers={"Content-Type": "application/json"},
|
|
329
361
|
)
|
|
330
362
|
|
|
363
|
+
if not upload_response.ok:
|
|
364
|
+
raise Exception(
|
|
365
|
+
f"Failed to upload manifest.json to signed URL: HTTP {upload_response.status_code}"
|
|
366
|
+
)
|
|
367
|
+
finally:
|
|
368
|
+
# Clean up temporary file
|
|
369
|
+
temp_file_path.unlink(missing_ok=True)
|
|
370
|
+
|
|
331
371
|
# Finalize the figure upload
|
|
332
372
|
print("Finalizing figure...")
|
|
333
373
|
_finalize_figure(figure_url, api_key)
|