figpack 0.1.0__py3-none-any.whl → 0.1.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.

File without changes
@@ -0,0 +1,46 @@
1
+ import os
2
+ import pathlib
3
+ import zarr
4
+ from .figpack_view import FigpackView
5
+
6
+ thisdir = pathlib.Path(__file__).parent.resolve()
7
+
8
+
9
+ def prepare_figure_bundle(view: FigpackView, tmpdir: str) -> None:
10
+ """
11
+ Prepare a figure bundle in the specified temporary directory.
12
+
13
+ This function:
14
+ 1. Copies all files from the figpack-gui-dist directory to tmpdir
15
+ 2. Writes the view data to a zarr group
16
+ 3. Consolidates zarr metadata
17
+
18
+ Args:
19
+ view: The figpack view to prepare
20
+ tmpdir: The temporary directory to prepare the bundle in
21
+ """
22
+ html_dir = thisdir / ".." / "figpack-gui-dist"
23
+ if not os.path.exists(html_dir):
24
+ raise SystemExit(f"Error: directory not found: {html_dir}")
25
+
26
+ # Copy all files in html_dir recursively to tmpdir
27
+ for item in html_dir.iterdir():
28
+ if item.is_file():
29
+ target = pathlib.Path(tmpdir) / item.name
30
+ target.write_bytes(item.read_bytes())
31
+ elif item.is_dir():
32
+ target = pathlib.Path(tmpdir) / item.name
33
+ target.mkdir(exist_ok=True)
34
+ for subitem in item.iterdir():
35
+ target_sub = target / subitem.name
36
+ target_sub.write_bytes(subitem.read_bytes())
37
+
38
+ # Write the graph data to the Zarr group
39
+ zarr_group = zarr.open_group(
40
+ pathlib.Path(tmpdir) / "data.zarr",
41
+ mode="w",
42
+ synchronizer=zarr.ThreadSynchronizer(),
43
+ )
44
+ view._write_to_zarr_group(zarr_group)
45
+
46
+ zarr.consolidate_metadata(zarr_group.store)
@@ -1,7 +1,6 @@
1
1
  import os
2
2
 
3
3
  from typing import Union
4
- import zarr
5
4
  import tempfile
6
5
 
7
6
  import webbrowser
@@ -11,43 +10,21 @@ import pathlib
11
10
  import threading
12
11
  from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
13
12
 
14
- from .views import TimeseriesGraph
13
+ from .figpack_view import FigpackView
14
+ from ._bundle_utils import prepare_figure_bundle
15
15
 
16
16
  thisdir = pathlib.Path(__file__).parent.resolve()
17
17
 
18
18
 
19
19
  def _show_view(
20
- view: TimeseriesGraph,
20
+ view: FigpackView,
21
21
  *,
22
22
  open_in_browser: bool = False,
23
23
  port: Union[int, None] = None,
24
24
  allow_origin: Union[str, None] = None,
25
25
  ):
26
26
  with tempfile.TemporaryDirectory(prefix="figpack_") as tmpdir:
27
- html_dir = thisdir / "figpack-gui-dist"
28
- if not os.path.exists(html_dir):
29
- raise SystemExit(f"Error: directory not found: {html_dir}")
30
- # copy all files in html_dir recursively to tmpdir
31
- for item in html_dir.iterdir():
32
- if item.is_file():
33
- target = pathlib.Path(tmpdir) / item.name
34
- target.write_bytes(item.read_bytes())
35
- elif item.is_dir():
36
- target = pathlib.Path(tmpdir) / item.name
37
- target.mkdir(exist_ok=True)
38
- for subitem in item.iterdir():
39
- target_sub = target / subitem.name
40
- target_sub.write_bytes(subitem.read_bytes())
41
-
42
- # Write the graph data to the Zarr group
43
- zarr_group = zarr.open_group(
44
- pathlib.Path(tmpdir) / "data.zarr",
45
- mode="w",
46
- synchronizer=zarr.ThreadSynchronizer(),
47
- )
48
- view._write_to_zarr_group(zarr_group)
49
-
50
- zarr.consolidate_metadata(zarr_group.store)
27
+ prepare_figure_bundle(view, tmpdir)
51
28
 
52
29
  serve_files(
53
30
  tmpdir,
@@ -5,11 +5,12 @@ import uuid
5
5
  import tempfile
6
6
  import pathlib
7
7
  import requests
8
+ import threading
9
+ from concurrent.futures import ThreadPoolExecutor, as_completed
8
10
  from datetime import datetime, timedelta, timezone
9
11
 
10
- import zarr
11
-
12
- from .views import TimeseriesGraph
12
+ from .figpack_view import FigpackView
13
+ from ._bundle_utils import prepare_figure_bundle
13
14
 
14
15
  thisdir = pathlib.Path(__file__).parent.resolve()
15
16
 
@@ -17,7 +18,7 @@ FIGPACK_API_BASE_URL = "https://figpack-api.vercel.app"
17
18
  TEMPORY_BASE_URL = "https://tempory.net/figpack/figures"
18
19
 
19
20
 
20
- def _upload_view(view: TimeseriesGraph) -> str:
21
+ def _upload_view(view: FigpackView) -> str:
21
22
  """
22
23
  Upload a figpack view to the cloud
23
24
 
@@ -45,7 +46,7 @@ def _upload_view(view: TimeseriesGraph) -> str:
45
46
  with tempfile.TemporaryDirectory(prefix="figpack_upload_") as tmpdir:
46
47
  # Prepare the figure bundle (reuse logic from _show_view)
47
48
  print("Preparing figure bundle...")
48
- _prepare_figure_bundle(view, tmpdir)
49
+ prepare_figure_bundle(view, tmpdir)
49
50
 
50
51
  # Upload the bundle
51
52
  print("Starting upload...")
@@ -58,40 +59,33 @@ def _upload_view(view: TimeseriesGraph) -> str:
58
59
  return figure_url
59
60
 
60
61
 
61
- def _prepare_figure_bundle(view: TimeseriesGraph, tmpdir: str) -> None:
62
+ def _upload_single_file(
63
+ figure_id: str, relative_path: str, file_path: pathlib.Path, passcode: str
64
+ ) -> str:
62
65
  """
63
- Prepare the figure bundle in the temporary directory
64
- This reuses the same logic as _show_view
66
+ Worker function to upload a single file
67
+
68
+ Returns:
69
+ str: The relative path of the uploaded file
65
70
  """
66
- html_dir = thisdir / "figpack-gui-dist"
67
- if not os.path.exists(html_dir):
68
- raise SystemExit(f"Error: directory not found: {html_dir}")
69
-
70
- # Copy all files in html_dir recursively to tmpdir
71
- for item in html_dir.iterdir():
72
- if item.is_file():
73
- target = pathlib.Path(tmpdir) / item.name
74
- target.write_bytes(item.read_bytes())
75
- elif item.is_dir():
76
- target = pathlib.Path(tmpdir) / item.name
77
- target.mkdir(exist_ok=True)
78
- for subitem in item.iterdir():
79
- target_sub = target / subitem.name
80
- target_sub.write_bytes(subitem.read_bytes())
81
-
82
- # Write the graph data to the Zarr group
83
- zarr_group = zarr.open_group(
84
- pathlib.Path(tmpdir) / "data.zarr",
85
- mode="w",
86
- synchronizer=zarr.ThreadSynchronizer(),
87
- )
88
- view._write_to_zarr_group(zarr_group)
89
- zarr.consolidate_metadata(zarr_group.store)
71
+ file_type = _determine_file_type(relative_path)
72
+
73
+ if file_type == "small":
74
+ with open(file_path, "r", encoding="utf-8") as f:
75
+ content = f.read()
76
+ _upload_small_file(figure_id, relative_path, content, passcode)
77
+ else: # large file
78
+ _upload_large_file(figure_id, relative_path, file_path, passcode)
79
+
80
+ return relative_path
81
+
82
+
83
+ MAX_WORKERS_FOR_UPLOAD = 16
90
84
 
91
85
 
92
86
  def _upload_bundle(tmpdir: str, figure_id: str, passcode: str) -> None:
93
87
  """
94
- Upload the prepared bundle to the cloud
88
+ Upload the prepared bundle to the cloud using parallel uploads
95
89
  """
96
90
  tmpdir_path = pathlib.Path(tmpdir)
97
91
 
@@ -116,42 +110,74 @@ def _upload_bundle(tmpdir: str, figure_id: str, passcode: str) -> None:
116
110
 
117
111
  print(f"Found {len(all_files)} files to upload")
118
112
 
119
- # Upload files
120
- uploaded_count = 0
121
- timer = time.time()
122
- for relative_path, file_path in all_files:
123
- # Skip the figpack.json since we already uploaded the initial version
124
- if relative_path == "figpack.json":
125
- continue
126
- file_type = _determine_file_type(relative_path)
127
-
128
- if file_type == "small":
129
- with open(file_path, "r", encoding="utf-8") as f:
130
- content = f.read()
131
- _upload_small_file(figure_id, relative_path, content, passcode)
132
- else: # large file
133
- _upload_large_file(figure_id, relative_path, file_path, passcode)
134
-
135
- uploaded_count += 1
136
- print(f"Uploaded {uploaded_count}/{len(all_files)-1}: {relative_path}")
137
- elapsed_time = time.time() - timer
138
- if elapsed_time > 60:
139
- figpack_json = {
140
- **figpack_json,
141
- "status": "uploading",
142
- "upload_progress": f"{uploaded_count}/{len(all_files)-1}",
143
- "upload_updated": datetime.now(timezone.utc).isoformat(),
113
+ # Filter out figpack.json since we already uploaded the initial version
114
+ files_to_upload = [
115
+ (rel_path, file_path)
116
+ for rel_path, file_path in all_files
117
+ if rel_path != "figpack.json"
118
+ ]
119
+ total_files_to_upload = len(files_to_upload)
120
+
121
+ if total_files_to_upload == 0:
122
+ print("No additional files to upload")
123
+ else:
124
+ print(
125
+ f"Uploading {total_files_to_upload} files with up to 8 concurrent uploads..."
126
+ )
127
+
128
+ # Thread-safe progress tracking
129
+ uploaded_count = 0
130
+ count_lock = threading.Lock()
131
+ timer = time.time()
132
+
133
+ # Upload files in parallel with concurrent uploads
134
+ with ThreadPoolExecutor(max_workers=MAX_WORKERS_FOR_UPLOAD) as executor:
135
+ # Submit all upload tasks
136
+ future_to_file = {
137
+ executor.submit(
138
+ _upload_single_file, figure_id, rel_path, file_path, passcode
139
+ ): rel_path
140
+ for rel_path, file_path in files_to_upload
144
141
  }
145
- _upload_small_file(
146
- figure_id,
147
- "figpack.json",
148
- json.dumps(figpack_json, indent=2),
149
- passcode,
150
- )
151
- print(
152
- f"Updated figpack.json with progress: {uploaded_count}/{len(all_files)-1}"
153
- )
154
- timer = time.time()
142
+
143
+ # Process completed uploads
144
+ for future in as_completed(future_to_file):
145
+ relative_path = future_to_file[future]
146
+ try:
147
+ future.result() # This will raise any exception that occurred during upload
148
+
149
+ # Thread-safe progress update
150
+ with count_lock:
151
+ uploaded_count += 1
152
+ print(
153
+ f"Uploaded {uploaded_count}/{total_files_to_upload}: {relative_path}"
154
+ )
155
+
156
+ # Update progress every 60 seconds
157
+ elapsed_time = time.time() - timer
158
+ if elapsed_time > 60:
159
+ figpack_json = {
160
+ **figpack_json,
161
+ "status": "uploading",
162
+ "upload_progress": f"{uploaded_count}/{total_files_to_upload}",
163
+ "upload_updated": datetime.now(
164
+ timezone.utc
165
+ ).isoformat(),
166
+ }
167
+ _upload_small_file(
168
+ figure_id,
169
+ "figpack.json",
170
+ json.dumps(figpack_json, indent=2),
171
+ passcode,
172
+ )
173
+ print(
174
+ f"Updated figpack.json with progress: {uploaded_count}/{total_files_to_upload}"
175
+ )
176
+ timer = time.time()
177
+
178
+ except Exception as e:
179
+ print(f"Failed to upload {relative_path}: {e}")
180
+ raise # Re-raise the exception to stop the upload process
155
181
 
156
182
  # Finally, upload completion status
157
183
  print("Uploading completion status...")