figpack 0.1.7__tar.gz → 0.2.1__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 figpack might be problematic. Click here for more details.

Files changed (63) hide show
  1. {figpack-0.1.7/figpack.egg-info → figpack-0.2.1}/PKG-INFO +1 -1
  2. {figpack-0.1.7 → figpack-0.2.1}/figpack/__init__.py +1 -1
  3. {figpack-0.1.7 → figpack-0.2.1}/figpack/core/_upload_bundle.py +113 -73
  4. figpack-0.1.7/figpack/figpack-gui-dist/assets/index-DaeClgi6.js → figpack-0.2.1/figpack/figpack-gui-dist/assets/index-BqYF6BN-.js +91 -91
  5. figpack-0.2.1/figpack/figpack-gui-dist/assets/index-Cmae55E4.css +1 -0
  6. {figpack-0.1.7 → figpack-0.2.1}/figpack/figpack-gui-dist/index.html +2 -2
  7. {figpack-0.1.7 → figpack-0.2.1/figpack.egg-info}/PKG-INFO +1 -1
  8. {figpack-0.1.7 → figpack-0.2.1}/figpack.egg-info/SOURCES.txt +3 -4
  9. {figpack-0.1.7 → figpack-0.2.1}/pyproject.toml +1 -1
  10. figpack-0.1.7/figpack/figpack-gui-dist/assets/index-BDa2iJW9.css +0 -1
  11. figpack-0.1.7/tests/test_upload_bundle.py +0 -679
  12. {figpack-0.1.7 → figpack-0.2.1}/LICENSE +0 -0
  13. {figpack-0.1.7 → figpack-0.2.1}/MANIFEST.in +0 -0
  14. {figpack-0.1.7 → figpack-0.2.1}/README.md +0 -0
  15. {figpack-0.1.7 → figpack-0.2.1}/figpack/cli.py +0 -0
  16. {figpack-0.1.7 → figpack-0.2.1}/figpack/core/__init__.py +0 -0
  17. {figpack-0.1.7 → figpack-0.2.1}/figpack/core/_bundle_utils.py +0 -0
  18. {figpack-0.1.7 → figpack-0.2.1}/figpack/core/_show_view.py +0 -0
  19. {figpack-0.1.7 → figpack-0.2.1}/figpack/core/config.py +0 -0
  20. {figpack-0.1.7 → figpack-0.2.1}/figpack/core/figpack_view.py +0 -0
  21. {figpack-0.1.7 → figpack-0.2.1}/figpack/figpack-gui-dist/assets/neurosift-logo-CLsuwLMO.png +0 -0
  22. {figpack-0.1.7 → figpack-0.2.1}/figpack/spike_sorting/__init__.py +0 -0
  23. {figpack-0.1.7 → figpack-0.2.1}/figpack/spike_sorting/views/AutocorrelogramItem.py +0 -0
  24. {figpack-0.1.7 → figpack-0.2.1}/figpack/spike_sorting/views/Autocorrelograms.py +0 -0
  25. {figpack-0.1.7 → figpack-0.2.1}/figpack/spike_sorting/views/CrossCorrelogramItem.py +0 -0
  26. {figpack-0.1.7 → figpack-0.2.1}/figpack/spike_sorting/views/CrossCorrelograms.py +0 -0
  27. {figpack-0.1.7 → figpack-0.2.1}/figpack/spike_sorting/views/UnitSimilarityScore.py +0 -0
  28. {figpack-0.1.7 → figpack-0.2.1}/figpack/spike_sorting/views/UnitsTable.py +0 -0
  29. {figpack-0.1.7 → figpack-0.2.1}/figpack/spike_sorting/views/UnitsTableColumn.py +0 -0
  30. {figpack-0.1.7 → figpack-0.2.1}/figpack/spike_sorting/views/UnitsTableRow.py +0 -0
  31. {figpack-0.1.7 → figpack-0.2.1}/figpack/spike_sorting/views/__init__.py +0 -0
  32. {figpack-0.1.7 → figpack-0.2.1}/figpack/views/Box.py +0 -0
  33. {figpack-0.1.7 → figpack-0.2.1}/figpack/views/Image.py +0 -0
  34. {figpack-0.1.7 → figpack-0.2.1}/figpack/views/LayoutItem.py +0 -0
  35. {figpack-0.1.7 → figpack-0.2.1}/figpack/views/Markdown.py +0 -0
  36. {figpack-0.1.7 → figpack-0.2.1}/figpack/views/MatplotlibFigure.py +0 -0
  37. {figpack-0.1.7 → figpack-0.2.1}/figpack/views/MultiChannelTimeseries.py +0 -0
  38. {figpack-0.1.7 → figpack-0.2.1}/figpack/views/PlotlyFigure.py +0 -0
  39. {figpack-0.1.7 → figpack-0.2.1}/figpack/views/Splitter.py +0 -0
  40. {figpack-0.1.7 → figpack-0.2.1}/figpack/views/TabLayout.py +0 -0
  41. {figpack-0.1.7 → figpack-0.2.1}/figpack/views/TabLayoutItem.py +0 -0
  42. {figpack-0.1.7 → figpack-0.2.1}/figpack/views/TimeseriesGraph.py +0 -0
  43. {figpack-0.1.7 → figpack-0.2.1}/figpack/views/__init__.py +0 -0
  44. {figpack-0.1.7 → figpack-0.2.1}/figpack.egg-info/dependency_links.txt +0 -0
  45. {figpack-0.1.7 → figpack-0.2.1}/figpack.egg-info/entry_points.txt +0 -0
  46. {figpack-0.1.7 → figpack-0.2.1}/figpack.egg-info/requires.txt +0 -0
  47. {figpack-0.1.7 → figpack-0.2.1}/figpack.egg-info/top_level.txt +0 -0
  48. {figpack-0.1.7 → figpack-0.2.1}/setup.cfg +0 -0
  49. {figpack-0.1.7 → figpack-0.2.1}/tests/test_box.py +0 -0
  50. {figpack-0.1.7 → figpack-0.2.1}/tests/test_cli.py +0 -0
  51. {figpack-0.1.7 → figpack-0.2.1}/tests/test_core.py +0 -0
  52. {figpack-0.1.7 → figpack-0.2.1}/tests/test_figpack_view.py +0 -0
  53. {figpack-0.1.7 → figpack-0.2.1}/tests/test_image.py +0 -0
  54. {figpack-0.1.7 → figpack-0.2.1}/tests/test_markdown.py +0 -0
  55. {figpack-0.1.7 → figpack-0.2.1}/tests/test_matplotlib_figure.py +0 -0
  56. {figpack-0.1.7 → figpack-0.2.1}/tests/test_multichannel_timeseries.py +0 -0
  57. {figpack-0.1.7 → figpack-0.2.1}/tests/test_plotly_figure.py +0 -0
  58. {figpack-0.1.7 → figpack-0.2.1}/tests/test_show_view.py +0 -0
  59. {figpack-0.1.7 → figpack-0.2.1}/tests/test_spike_sorting_correlograms.py +0 -0
  60. {figpack-0.1.7 → figpack-0.2.1}/tests/test_splitter.py +0 -0
  61. {figpack-0.1.7 → figpack-0.2.1}/tests/test_tablayout.py +0 -0
  62. {figpack-0.1.7 → figpack-0.2.1}/tests/test_timeseries_graph.py +0 -0
  63. {figpack-0.1.7 → figpack-0.2.1}/tests/test_units_table.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: figpack
3
- Version: 0.1.7
3
+ Version: 0.2.1
4
4
  Summary: A Python package for creating shareable, interactive visualizations in the browser
5
5
  Author-email: Jeremy Magland <jmagland@flatironinstitute.org>
6
6
  License: Apache-2.0
@@ -2,4 +2,4 @@
2
2
  figpack - A Python package for creating shareable, interactive visualizations in the browser
3
3
  """
4
4
 
5
- __version__ = "0.1.7"
5
+ __version__ = "0.2.1"
@@ -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 _upload_single_file(
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
- Worker function to upload a single file using signed URL
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
- str: The relative path of the uploaded file
29
+ dict: Mapping of relative_path to signed_url
27
30
  """
28
- file_size = file_path.stat().st_size
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
- "relativePath": relative_path,
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 URL for {relative_path}: {error_msg}")
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 URL for {relative_path}: {response_data.get('message', 'Unknown error')}"
56
+ f"Failed to get signed URLs for batch: {response_data.get('message', 'Unknown error')}"
52
57
  )
53
58
 
54
- signed_url = response_data.get("signedUrl")
55
- if not signed_url:
56
- raise Exception(f"No signed URL returned for {relative_path}")
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
- # Upload files in parallel with concurrent uploads
246
- with ThreadPoolExecutor(max_workers=MAX_WORKERS_FOR_UPLOAD) as executor:
247
- # Submit all upload tasks
248
- future_to_file = {
249
- executor.submit(
250
- _upload_single_file, figure_url, rel_path, file_path, api_key
251
- ): rel_path
252
- for rel_path, file_path in files_to_upload
253
- }
254
-
255
- # Process completed uploads
256
- for future in as_completed(future_to_file):
257
- relative_path = future_to_file[future]
258
- try:
259
- future.result() # This will raise any exception that occurred during upload
260
-
261
- # Thread-safe progress update
262
- with count_lock:
263
- uploaded_count += 1
264
- print(
265
- f"Uploaded {uploaded_count}/{total_files_to_upload}: {relative_path}"
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
- except Exception as e:
269
- print(f"Failed to upload {relative_path}: {e}")
270
- raise # Re-raise the exception to stop the upload process
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
- manifest_payload = {
294
- "figureUrl": figure_url,
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
- response_data = response.json()
312
- if not response_data.get("success"):
313
- raise Exception(
314
- f"Failed to get signed URL for manifest.json: {response_data.get('message', 'Unknown error')}"
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
- signed_url = response_data.get("signedUrl")
318
- if not signed_url:
319
- raise Exception("No signed URL returned for manifest.json")
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
- # Upload manifest using signed URL
322
- upload_response = requests.put(
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
- if not upload_response.ok:
327
- raise Exception(
328
- f"Failed to upload manifest.json to signed URL: HTTP {upload_response.status_code}"
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)