figpack 0.1.4__py3-none-any.whl → 0.1.6__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.

Files changed (35) hide show
  1. figpack/__init__.py +1 -5
  2. figpack/cli.py +8 -7
  3. figpack/core/_bundle_utils.py +2 -0
  4. figpack/core/_show_view.py +8 -12
  5. figpack/core/_upload_bundle.py +167 -275
  6. figpack/core/config.py +1 -0
  7. figpack/core/figpack_view.py +1 -0
  8. figpack/figpack-gui-dist/assets/{index-Dw14QqeQ.js → index-DaeClgi6.js} +91 -91
  9. figpack/figpack-gui-dist/index.html +1 -1
  10. figpack/spike_sorting/views/AutocorrelogramItem.py +1 -0
  11. figpack/spike_sorting/views/Autocorrelograms.py +39 -9
  12. figpack/spike_sorting/views/CrossCorrelogramItem.py +1 -0
  13. figpack/spike_sorting/views/CrossCorrelograms.py +45 -7
  14. figpack/spike_sorting/views/UnitsTable.py +5 -3
  15. figpack/spike_sorting/views/UnitsTableRow.py +1 -1
  16. figpack/spike_sorting/views/__init__.py +2 -2
  17. figpack/views/Box.py +9 -1
  18. figpack/views/Image.py +12 -2
  19. figpack/views/LayoutItem.py +1 -0
  20. figpack/views/Markdown.py +1 -0
  21. figpack/views/MatplotlibFigure.py +4 -2
  22. figpack/views/MultiChannelTimeseries.py +230 -0
  23. figpack/views/PlotlyFigure.py +5 -3
  24. figpack/views/Splitter.py +3 -1
  25. figpack/views/TabLayout.py +3 -1
  26. figpack/views/TabLayoutItem.py +1 -0
  27. figpack/views/TimeseriesGraph.py +3 -2
  28. figpack/views/__init__.py +7 -6
  29. {figpack-0.1.4.dist-info → figpack-0.1.6.dist-info}/METADATA +49 -8
  30. figpack-0.1.6.dist-info/RECORD +40 -0
  31. figpack-0.1.4.dist-info/RECORD +0 -38
  32. {figpack-0.1.4.dist-info → figpack-0.1.6.dist-info}/WHEEL +0 -0
  33. {figpack-0.1.4.dist-info → figpack-0.1.6.dist-info}/entry_points.txt +0 -0
  34. {figpack-0.1.4.dist-info → figpack-0.1.6.dist-info}/licenses/LICENSE +0 -0
  35. {figpack-0.1.4.dist-info → figpack-0.1.6.dist-info}/top_level.txt +0 -0
figpack/__init__.py CHANGED
@@ -2,8 +2,4 @@
2
2
  figpack - A Python package for creating shareable, interactive visualizations in the browser
3
3
  """
4
4
 
5
- __version__ = "0.1.4"
6
- __author__ = "Jeremy Magland"
7
- __email__ = "jmagland@flatironinstitute.org"
8
-
9
- from . import spike_sorting
5
+ __version__ = "0.1.6"
figpack/cli.py CHANGED
@@ -3,19 +3,20 @@ Command-line interface for figpack
3
3
  """
4
4
 
5
5
  import argparse
6
- import sys
6
+ import json
7
7
  import pathlib
8
- import tempfile
8
+ import socket
9
+ import sys
9
10
  import tarfile
10
- import json
11
- import requests
11
+ import tempfile
12
12
  import threading
13
13
  import webbrowser
14
- import socket
15
14
  from concurrent.futures import ThreadPoolExecutor, as_completed
16
- from urllib.parse import urlparse, urljoin
17
- from typing import Dict, List, Tuple, Optional, Union
18
15
  from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
16
+ from typing import Dict, List, Optional, Tuple, Union
17
+ from urllib.parse import urljoin, urlparse
18
+
19
+ import requests
19
20
 
20
21
  from . import __version__
21
22
  from .core._show_view import serve_files
@@ -1,6 +1,8 @@
1
1
  import os
2
2
  import pathlib
3
+
3
4
  import zarr
5
+
4
6
  from .figpack_view import FigpackView
5
7
 
6
8
  thisdir = pathlib.Path(__file__).parent.resolve()
@@ -1,18 +1,14 @@
1
1
  import os
2
-
3
- from typing import Union
4
- import tempfile
5
-
6
- import webbrowser
7
-
8
2
  import pathlib
9
-
3
+ import tempfile
10
4
  import threading
5
+ import webbrowser
11
6
  from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
7
+ from typing import Union
12
8
 
13
- from .figpack_view import FigpackView
14
9
  from ._bundle_utils import prepare_figure_bundle
15
10
  from ._upload_bundle import _upload_bundle
11
+ from .figpack_view import FigpackView
16
12
 
17
13
  thisdir = pathlib.Path(__file__).parent.resolve()
18
14
 
@@ -32,15 +28,15 @@ def _show_view(
32
28
 
33
29
  if upload:
34
30
  # Check for required environment variable
35
- passcode = os.environ.get("FIGPACK_UPLOAD_PASSCODE")
36
- if not passcode:
31
+ api_key = os.environ.get("FIGPACK_API_KEY")
32
+ if not api_key:
37
33
  raise EnvironmentError(
38
- "FIGPACK_UPLOAD_PASSCODE environment variable must be set to upload views."
34
+ "FIGPACK_API_KEY environment variable must be set to upload views."
39
35
  )
40
36
 
41
37
  # Upload the bundle
42
38
  print("Starting upload...")
43
- figure_url = _upload_bundle(tmpdir, passcode)
39
+ figure_url = _upload_bundle(tmpdir, api_key)
44
40
 
45
41
  if open_in_browser:
46
42
  webbrowser.open(figure_url)
@@ -1,37 +1,71 @@
1
- import time
1
+ import hashlib
2
2
  import json
3
- import uuid
4
3
  import pathlib
5
- import requests
6
4
  import threading
7
- import hashlib
8
- from .. import __version__
5
+ import time
6
+ import uuid
9
7
  from concurrent.futures import ThreadPoolExecutor, as_completed
10
8
  from datetime import datetime, timedelta, timezone
11
9
 
12
- thisdir = pathlib.Path(__file__).parent.resolve()
10
+ import requests
11
+
12
+ from .. import __version__
13
13
 
14
- FIGPACK_API_BASE_URL = "https://figpack-api.vercel.app"
15
- TEMPORY_BASE_URL = "https://tempory.net/figpack/default/figures"
14
+ from .config import FIGPACK_API_BASE_URL
15
+
16
+ thisdir = pathlib.Path(__file__).parent.resolve()
16
17
 
17
18
 
18
19
  def _upload_single_file(
19
- figure_id: str, relative_path: str, file_path: pathlib.Path, passcode: str
20
+ figure_url: str, relative_path: str, file_path: pathlib.Path, api_key: str
20
21
  ) -> str:
21
22
  """
22
- Worker function to upload a single file
23
+ Worker function to upload a single file using signed URL
23
24
 
24
25
  Returns:
25
26
  str: The relative path of the uploaded file
26
27
  """
27
- file_type = _determine_file_type(relative_path)
28
+ file_size = file_path.stat().st_size
29
+
30
+ # Get signed URL
31
+ payload = {
32
+ "figureUrl": figure_url,
33
+ "relativePath": relative_path,
34
+ "apiKey": api_key,
35
+ "size": file_size,
36
+ }
37
+
38
+ response = requests.post(f"{FIGPACK_API_BASE_URL}/api/upload", json=payload)
39
+
40
+ if not response.ok:
41
+ try:
42
+ error_data = response.json()
43
+ error_msg = error_data.get("message", "Unknown error")
44
+ except:
45
+ error_msg = f"HTTP {response.status_code}"
46
+ raise Exception(f"Failed to get signed URL for {relative_path}: {error_msg}")
28
47
 
29
- if file_type == "small":
30
- with open(file_path, "r", encoding="utf-8") as f:
31
- content = f.read()
32
- _upload_small_file(figure_id, relative_path, content, passcode)
33
- else: # large file
34
- _upload_large_file(figure_id, relative_path, file_path, passcode)
48
+ response_data = response.json()
49
+ if not response_data.get("success"):
50
+ raise Exception(
51
+ f"Failed to get signed URL for {relative_path}: {response_data.get('message', 'Unknown error')}"
52
+ )
53
+
54
+ signed_url = response_data.get("signedUrl")
55
+ if not signed_url:
56
+ raise Exception(f"No signed URL returned for {relative_path}")
57
+
58
+ # Upload file to signed URL
59
+ content_type = _determine_content_type(relative_path)
60
+ with open(file_path, "rb") as f:
61
+ upload_response = requests.put(
62
+ signed_url, data=f, headers={"Content-Type": content_type}
63
+ )
64
+
65
+ if not upload_response.ok:
66
+ raise Exception(
67
+ f"Failed to upload {relative_path} to signed URL: HTTP {upload_response.status_code}"
68
+ )
35
69
 
36
70
  return relative_path
37
71
 
@@ -39,7 +73,7 @@ def _upload_single_file(
39
73
  MAX_WORKERS_FOR_UPLOAD = 16
40
74
 
41
75
 
42
- def _compute_deterministic_figure_id(tmpdir_path: pathlib.Path) -> str:
76
+ def _compute_deterministic_figure_hash(tmpdir_path: pathlib.Path) -> str:
43
77
  """
44
78
  Compute a deterministic figure ID based on SHA1 hashes of all files
45
79
 
@@ -71,101 +105,88 @@ def _compute_deterministic_figure_id(tmpdir_path: pathlib.Path) -> str:
71
105
  return combined_hash.hexdigest()
72
106
 
73
107
 
74
- def _check_existing_figure(figure_id: str) -> dict:
108
+ def _create_or_get_figure(
109
+ figure_hash: str, api_key: str, total_files: int = None, total_size: int = None
110
+ ) -> dict:
75
111
  """
76
- Check if a figure already exists and return its status
112
+ Create a new figure or get existing figure information
77
113
 
78
114
  Returns:
79
- dict: Contains 'exists' (bool) and 'status' (str) if exists
115
+ dict: Figure information from the API
80
116
  """
81
- figpack_url = f"{TEMPORY_BASE_URL}/{figure_id}/figpack.json"
117
+ payload = {
118
+ "figureHash": figure_hash,
119
+ "apiKey": api_key,
120
+ "figpackVersion": __version__,
121
+ }
82
122
 
83
- try:
84
- response = requests.get(figpack_url, timeout=10)
85
- if response.ok:
86
- figpack_data = response.json()
87
- return {"exists": True, "status": figpack_data.get("status", "unknown")}
88
- else:
89
- return {"exists": False}
90
- except Exception:
91
- return {"exists": False}
123
+ if total_files is not None:
124
+ payload["totalFiles"] = total_files
125
+ if total_size is not None:
126
+ payload["totalSize"] = total_size
92
127
 
128
+ response = requests.post(f"{FIGPACK_API_BASE_URL}/api/figures/create", json=payload)
93
129
 
94
- def _find_available_figure_id(base_figure_id: str) -> tuple:
130
+ if not response.ok:
131
+ try:
132
+ error_data = response.json()
133
+ error_msg = error_data.get("message", "Unknown error")
134
+ except:
135
+ error_msg = f"HTTP {response.status_code}"
136
+ raise Exception(f"Failed to create figure {figure_hash}: {error_msg}")
137
+
138
+ response_data = response.json()
139
+ if not response_data.get("success"):
140
+ raise Exception(
141
+ f"Failed to create figure {figure_hash}: {response_data.get('message', 'Unknown error')}"
142
+ )
143
+
144
+ return response_data
145
+
146
+
147
+ def _finalize_figure(figure_url: str, api_key: str) -> dict:
95
148
  """
96
- Find an available figure ID by checking base_figure_id, then base_figure_id-1, base_figure_id-2, etc.
149
+ Finalize a figure upload
97
150
 
98
151
  Returns:
99
- tuple: (figure_id_to_use, completed_figure_id) where:
100
- - figure_id_to_use is None if upload should be skipped
101
- - completed_figure_id is the ID of the completed figure if one exists
152
+ dict: Figure information from the API
102
153
  """
103
- # First check the base figure ID
104
- result = _check_existing_figure(base_figure_id)
105
- if not result["exists"]:
106
- return (base_figure_id, None)
107
- elif result["status"] == "completed":
108
- print(
109
- f"Figure {base_figure_id} already exists and is completed. Skipping upload."
154
+ payload = {
155
+ "figureUrl": figure_url,
156
+ "apiKey": api_key,
157
+ }
158
+
159
+ response = requests.post(
160
+ f"{FIGPACK_API_BASE_URL}/api/figures/finalize", json=payload
161
+ )
162
+
163
+ if not response.ok:
164
+ try:
165
+ error_data = response.json()
166
+ error_msg = error_data.get("message", "Unknown error")
167
+ except:
168
+ error_msg = f"HTTP {response.status_code}"
169
+ raise Exception(f"Failed to finalize figure {figure_url}: {error_msg}")
170
+
171
+ response_data = response.json()
172
+ if not response_data.get("success"):
173
+ raise Exception(
174
+ f"Failed to finalize figure {figure_url}: {response_data.get('message', 'Unknown error')}"
110
175
  )
111
- return (None, base_figure_id) # Signal to skip upload, return completed ID
112
-
113
- # If exists but not completed, try with suffixes
114
- suffix = 1
115
- while True:
116
- candidate_id = f"{base_figure_id}-{suffix}"
117
- result = _check_existing_figure(candidate_id)
118
-
119
- if not result["exists"]:
120
- print(f"Using figure ID: {candidate_id}")
121
- return (candidate_id, None)
122
- elif result["status"] == "completed":
123
- print(
124
- f"Figure {candidate_id} already exists and is completed. Skipping upload."
125
- )
126
- return (None, candidate_id) # Signal to skip upload, return completed ID
127
-
128
- suffix += 1
129
- if suffix > 100: # Safety limit
130
- raise Exception(
131
- "Too many existing figure variants, unable to find available ID"
132
- )
133
-
134
-
135
- def _upload_bundle(tmpdir: str, passcode: str) -> None:
176
+
177
+ return response_data
178
+
179
+
180
+ def _upload_bundle(tmpdir: str, api_key: str) -> str:
136
181
  """
137
- Upload the prepared bundle to the cloud using parallel uploads
182
+ Upload the prepared bundle to the cloud using the new database-driven approach
138
183
  """
139
184
  tmpdir_path = pathlib.Path(tmpdir)
140
185
 
141
186
  # Compute deterministic figure ID based on file contents
142
187
  print("Computing deterministic figure ID...")
143
- base_figure_id = _compute_deterministic_figure_id(tmpdir_path)
144
- print(f"Base figure ID: {base_figure_id}")
145
-
146
- # Find available figure ID (check for existing uploads)
147
- figure_id, completed_figure_id = _find_available_figure_id(base_figure_id)
148
-
149
- # If figure_id is None, it means we found a completed upload and should skip
150
- if figure_id is None:
151
- figure_url = f"{TEMPORY_BASE_URL}/{completed_figure_id}/index.html"
152
- print(f"Figure already exists at: {figure_url}")
153
- return figure_url
154
-
155
- print(f"Using figure ID: {figure_id}")
156
-
157
- # First, upload initial figpack.json with "uploading" status
158
- print("Uploading initial status...")
159
- figpack_json = {
160
- "status": "uploading",
161
- "upload_started": datetime.now(timezone.utc).isoformat(),
162
- "upload_updated": datetime.now(timezone.utc).isoformat(),
163
- "figure_id": figure_id,
164
- "figpack_version": __version__,
165
- }
166
- _upload_small_file(
167
- figure_id, "figpack.json", json.dumps(figpack_json, indent=2), passcode
168
- )
188
+ figure_hash = _compute_deterministic_figure_hash(tmpdir_path)
189
+ print(f"Figure hash: {figure_hash}")
169
190
 
170
191
  # Collect all files to upload
171
192
  all_files = []
@@ -174,34 +195,44 @@ def _upload_bundle(tmpdir: str, passcode: str) -> None:
174
195
  relative_path = file_path.relative_to(tmpdir_path)
175
196
  all_files.append((str(relative_path), file_path))
176
197
 
177
- print(f"Found {len(all_files)} files to upload")
198
+ # Calculate total files and size for metadata
199
+ total_files = len(all_files)
200
+ total_size = sum(file_path.stat().st_size for _, file_path in all_files)
201
+ print(
202
+ f"Found {total_files} files to upload, total size: {total_size / (1024 * 1024):.2f} MB"
203
+ )
204
+
205
+ # Find available figure ID and create/get figure in database with metadata
206
+ result = _create_or_get_figure(figure_hash, api_key, total_files, total_size)
207
+ figure_info = result.get("figure", {})
208
+ figure_url = figure_info.get("figureUrl")
178
209
 
179
- # Filter out figpack.json since we already uploaded the initial version
180
- files_to_upload = [
181
- (rel_path, file_path)
182
- for rel_path, file_path in all_files
183
- if rel_path != "figpack.json"
184
- ]
210
+ if figure_info["status"] == "completed":
211
+ print(f"Figure already exists at: {figure_url}")
212
+ return figure_url
213
+
214
+ print(f"Using figure URL: {figure_url}")
215
+
216
+ files_to_upload = all_files
185
217
  total_files_to_upload = len(files_to_upload)
186
218
 
187
219
  if total_files_to_upload == 0:
188
- print("No additional files to upload")
220
+ print("No files to upload")
189
221
  else:
190
222
  print(
191
- f"Uploading {total_files_to_upload} files with up to 8 concurrent uploads..."
223
+ f"Uploading {total_files_to_upload} files with up to {MAX_WORKERS_FOR_UPLOAD} concurrent uploads..."
192
224
  )
193
225
 
194
226
  # Thread-safe progress tracking
195
227
  uploaded_count = 0
196
228
  count_lock = threading.Lock()
197
- timer = time.time()
198
229
 
199
230
  # Upload files in parallel with concurrent uploads
200
231
  with ThreadPoolExecutor(max_workers=MAX_WORKERS_FOR_UPLOAD) as executor:
201
232
  # Submit all upload tasks
202
233
  future_to_file = {
203
234
  executor.submit(
204
- _upload_single_file, figure_id, rel_path, file_path, passcode
235
+ _upload_single_file, figure_url, rel_path, file_path, api_key
205
236
  ): rel_path
206
237
  for rel_path, file_path in files_to_upload
207
238
  }
@@ -219,36 +250,14 @@ def _upload_bundle(tmpdir: str, passcode: str) -> None:
219
250
  f"Uploaded {uploaded_count}/{total_files_to_upload}: {relative_path}"
220
251
  )
221
252
 
222
- # Update progress every 60 seconds
223
- elapsed_time = time.time() - timer
224
- if elapsed_time > 60:
225
- figpack_json = {
226
- **figpack_json,
227
- "status": "uploading",
228
- "upload_progress": f"{uploaded_count}/{total_files_to_upload}",
229
- "upload_updated": datetime.now(
230
- timezone.utc
231
- ).isoformat(),
232
- }
233
- _upload_small_file(
234
- figure_id,
235
- "figpack.json",
236
- json.dumps(figpack_json, indent=2),
237
- passcode,
238
- )
239
- print(
240
- f"Updated figpack.json with progress: {uploaded_count}/{total_files_to_upload}"
241
- )
242
- timer = time.time()
243
-
244
253
  except Exception as e:
245
254
  print(f"Failed to upload {relative_path}: {e}")
246
255
  raise # Re-raise the exception to stop the upload process
247
256
 
248
- # Create and upload manifest.json
249
- print("Creating manifest.json...")
257
+ # Create manifest for finalization
258
+ print("Creating manifest...")
250
259
  manifest = {
251
- "timestamp": datetime.now(timezone.utc).isoformat(),
260
+ "timestamp": time.time(),
252
261
  "files": [],
253
262
  "total_size": 0,
254
263
  "total_files": len(files_to_upload),
@@ -259,175 +268,58 @@ def _upload_bundle(tmpdir: str, passcode: str) -> None:
259
268
  manifest["files"].append({"path": rel_path, "size": file_size})
260
269
  manifest["total_size"] += file_size
261
270
 
262
- _upload_small_file(
263
- figure_id, "manifest.json", json.dumps(manifest, indent=2), passcode
264
- )
265
- print("Uploaded manifest.json")
266
271
  print(f"Total size: {manifest['total_size'] / (1024 * 1024):.2f} MB")
267
272
 
268
- # Finally, upload completion status
269
- figpack_json = {
270
- **figpack_json,
271
- "status": "completed",
272
- "upload_completed": datetime.now(timezone.utc).isoformat(),
273
- "expiration": (datetime.now(timezone.utc) + timedelta(days=1)).isoformat(),
274
- "figure_id": figure_id,
275
- "total_files": len(all_files),
276
- "total_size": manifest["total_size"],
277
- "figpack_version": __version__,
278
- }
279
- _upload_small_file(
280
- figure_id, "figpack.json", json.dumps(figpack_json, indent=2), passcode
281
- )
282
- print("Upload completed successfully")
283
-
284
- figure_url = f"{TEMPORY_BASE_URL}/{figure_id}/index.html"
285
- return figure_url
286
-
287
-
288
- def _determine_file_type(file_path: str) -> str:
289
- """
290
- Determine if a file should be uploaded as small or large
291
- Based on the validation logic in the API
292
- """
293
- # Check exact matches first
294
- if file_path == "figpack.json" or file_path == "index.html":
295
- return "small"
296
-
297
- # Check zarr metadata files
298
- if (
299
- file_path.endswith(".zattrs")
300
- or file_path.endswith(".zgroup")
301
- or file_path.endswith(".zarray")
302
- or file_path.endswith(".zmetadata")
303
- ):
304
- return "small"
305
-
306
- # Check HTML files
307
- if file_path.endswith(".html"):
308
- return "small"
309
-
310
- # Check data.zarr directory
311
- if file_path.startswith("data.zarr/"):
312
- file_name = file_path[len("data.zarr/") :]
313
- # Check if it's a zarr chunk (numeric like 0.0.1)
314
- if _is_zarr_chunk(file_name):
315
- return "large"
316
- # Check for zarr metadata files in subdirectories
317
- if (
318
- file_name.endswith(".zattrs")
319
- or file_name.endswith(".zgroup")
320
- or file_name.endswith(".zarray")
321
- or file_name.endswith(".zmetadata")
322
- ):
323
- return "small"
324
-
325
- # Check assets directory
326
- if file_path.startswith("assets/"):
327
- file_name = file_path[len("assets/") :]
328
- if file_name.endswith(".js") or file_name.endswith(".css"):
329
- return "large"
330
-
331
- # Default to large file
332
- return "large"
333
-
334
-
335
- def _is_zarr_chunk(file_name: str) -> bool:
336
- """
337
- Check if filename consists only of numbers and dots (zarr chunk pattern)
338
- """
339
- for char in file_name:
340
- if char != "." and not char.isdigit():
341
- return False
342
- return (
343
- len(file_name) > 0
344
- and not file_name.startswith(".")
345
- and not file_name.endswith(".")
346
- )
273
+ # Upload manifest.json
274
+ print("Uploading manifest.json...")
275
+ manifest_content = json.dumps(manifest, indent=2)
276
+ manifest_size = len(manifest_content.encode("utf-8"))
347
277
 
348
-
349
- def _upload_small_file(
350
- figure_id: str, file_path: str, content: str, passcode: str
351
- ) -> None:
352
- """
353
- Upload a small file by sending content directly
354
- """
355
- destination_url = f"{TEMPORY_BASE_URL}/{figure_id}/{file_path}"
356
-
357
- try:
358
- content.encode("utf-8")
359
- except Exception as e:
360
- raise Exception(f"Content for {file_path} is not UTF-8 encodable: {e}")
361
- payload = {
362
- "destinationUrl": destination_url,
363
- "passcode": passcode,
364
- "content": content,
278
+ manifest_payload = {
279
+ "figureUrl": figure_url,
280
+ "relativePath": "manifest.json",
281
+ "apiKey": api_key,
282
+ "size": manifest_size,
365
283
  }
366
- # check that payload is json serializable
367
- try:
368
- json.dumps(payload)
369
- except Exception as e:
370
- raise Exception(f"Payload for {file_path} is not JSON serializable: {e}")
371
-
372
- response = requests.post(f"{FIGPACK_API_BASE_URL}/api/upload", json=payload)
373
-
374
- if not response.ok:
375
- try:
376
- error_data = response.json()
377
- error_msg = error_data.get("message", "Unknown error")
378
- except:
379
- error_msg = f"HTTP {response.status_code}"
380
- raise Exception(f"Failed to upload {file_path}: {error_msg}")
381
-
382
-
383
- def _upload_large_file(
384
- figure_id: str, file_path: str, local_file_path: pathlib.Path, passcode: str
385
- ) -> None:
386
- """
387
- Upload a large file using signed URL
388
- """
389
- destination_url = f"{TEMPORY_BASE_URL}/{figure_id}/{file_path}"
390
- file_size = local_file_path.stat().st_size
391
-
392
- # Get signed URL
393
- payload = {
394
- "destinationUrl": destination_url,
395
- "passcode": passcode,
396
- "size": file_size,
397
- }
398
-
399
- response = requests.post(f"{FIGPACK_API_BASE_URL}/api/upload", json=payload)
400
284
 
285
+ response = requests.post(
286
+ f"{FIGPACK_API_BASE_URL}/api/upload", json=manifest_payload
287
+ )
401
288
  if not response.ok:
402
289
  try:
403
290
  error_data = response.json()
404
291
  error_msg = error_data.get("message", "Unknown error")
405
292
  except:
406
293
  error_msg = f"HTTP {response.status_code}"
407
- raise Exception(f"Failed to get signed URL for {file_path}: {error_msg}")
294
+ raise Exception(f"Failed to get signed URL for manifest.json: {error_msg}")
408
295
 
409
296
  response_data = response.json()
410
297
  if not response_data.get("success"):
411
298
  raise Exception(
412
- f"Failed to get signed URL for {file_path}: {response_data.get('message', 'Unknown error')}"
299
+ f"Failed to get signed URL for manifest.json: {response_data.get('message', 'Unknown error')}"
413
300
  )
414
301
 
415
302
  signed_url = response_data.get("signedUrl")
416
303
  if not signed_url:
417
- raise Exception(f"No signed URL returned for {file_path}")
304
+ raise Exception("No signed URL returned for manifest.json")
418
305
 
419
- # Upload file to signed URL
420
- content_type = _determine_content_type(file_path)
421
- with open(local_file_path, "rb") as f:
422
- upload_response = requests.put(
423
- signed_url, data=f, headers={"Content-Type": content_type}
424
- )
306
+ # Upload manifest using signed URL
307
+ upload_response = requests.put(
308
+ signed_url, data=manifest_content, headers={"Content-Type": "application/json"}
309
+ )
425
310
 
426
311
  if not upload_response.ok:
427
312
  raise Exception(
428
- f"Failed to upload {file_path} to signed URL: HTTP {upload_response.status_code}"
313
+ f"Failed to upload manifest.json to signed URL: HTTP {upload_response.status_code}"
429
314
  )
430
315
 
316
+ # Finalize the figure upload
317
+ print("Finalizing figure...")
318
+ _finalize_figure(figure_url, api_key)
319
+ print("Upload completed successfully")
320
+
321
+ return figure_url
322
+
431
323
 
432
324
  def _determine_content_type(file_path: str) -> str:
433
325
  """
figpack/core/config.py ADDED
@@ -0,0 +1 @@
1
+ FIGPACK_API_BASE_URL = "https://figpack-api.vercel.app"
@@ -49,6 +49,7 @@ class FigpackView:
49
49
  print(
50
50
  f"For development, run figpack-gui in dev mode and use http://localhost:5173?data=http://localhost:{port}/data.zarr"
51
51
  )
52
+ open_in_browser = False
52
53
 
53
54
  _show_view(
54
55
  self,