qplotter 1.6.0__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.
- qplot/__init__.py +46 -0
- qplot/__main__.py +232 -0
- qplot/_auxiliary_paths.py +67 -0
- qplot/_build_info.json +1 -0
- qplot/_metadata.py +75 -0
- qplot/_repair.py +14 -0
- qplot/_shutdown_supervisor.py +4049 -0
- qplot/_version.py +12 -0
- qplot/_windows_shutdown_job.py +1166 -0
- qplot/configuration/__init__.py +3 -0
- qplot/configuration/config.py +578 -0
- qplot/configuration/config_schema.json +357 -0
- qplot/configuration/fonts.py +3 -0
- qplot/configuration/scripts.py +255 -0
- qplot/configuration/themes/__init__.py +13 -0
- qplot/configuration/themes/_base.py +592 -0
- qplot/configuration/themes/blank.py +17 -0
- qplot/configuration/themes/dark.py +54 -0
- qplot/configuration/themes/light.py +54 -0
- qplot/datahandling/LoadFromDB.py +224 -0
- qplot/datahandling/__init__.py +47 -0
- qplot/datahandling/_trusted_live_helper.py +812 -0
- qplot/datahandling/_trusted_live_protocol.py +1745 -0
- qplot/datahandling/database.py +2340 -0
- qplot/datahandling/dimensions.py +43 -0
- qplot/datahandling/file_identity.py +461 -0
- qplot/datahandling/parameter_data.py +149 -0
- qplot/datahandling/plot_progress.py +17 -0
- qplot/datahandling/qcodes_cache.py +277 -0
- qplot/datahandling/qcodes_compat.py +14 -0
- qplot/datahandling/readSQL.py +2442 -0
- qplot/datahandling/readonly.py +2566 -0
- qplot/datahandling/source_activity.py +21 -0
- qplot/datahandling/trusted_array_preview.py +148 -0
- qplot/datahandling/trusted_derived_cache.py +1605 -0
- qplot/datahandling/trusted_derived_rendering.py +876 -0
- qplot/datahandling/trusted_heatmap.py +246 -0
- qplot/datahandling/trusted_live.py +2808 -0
- qplot/datahandling/trusted_live_queries.py +5907 -0
- qplot/datahandling/trusted_live_service.py +1919 -0
- qplot/datahandling/trusted_live_supervisor.py +2161 -0
- qplot/datahandling/trusted_plot.py +283 -0
- qplot/datahandling/trusted_presentation.py +1065 -0
- qplot/datahandling/trusted_snapshot.py +1132 -0
- qplot/datahandling/trusted_work_coordinator.py +2157 -0
- qplot/datahandling/trusted_work_scheduler.py +1204 -0
- qplot/diagnostics.py +241 -0
- qplot/resources/testdata/qplot_test_db_01_10mb.csv +11 -0
- qplot/resources/testdata/qplot_test_db_02_25mb.csv +14 -0
- qplot/resources/testdata/qplot_test_db_03_50mb.csv +17 -0
- qplot/resources/testdata/qplot_test_db_04_100mb.csv +20 -0
- qplot/resources/testdata/qplot_test_db_05_250mb.csv +23 -0
- qplot/resources/testdata/qplot_test_db_06_500mb.csv +26 -0
- qplot/resources/testdata/qplot_test_db_07_1gb.csv +29 -0
- qplot/resources/testdata/qplot_test_db_08_5gb.csv +32 -0
- qplot/resources/testdata/qplot_test_db_09_10gb.csv +35 -0
- qplot/resources/testdata/qplot_test_db_10_30gb.csv +38 -0
- qplot/testdata.py +1804 -0
- qplot/tools/__init__.py +13 -0
- qplot/tools/finite_means.py +123 -0
- qplot/tools/general.py +139 -0
- qplot/tools/heatmap_geometry.py +391 -0
- qplot/tools/operation_registry.py +179 -0
- qplot/tools/plot_tools.py +650 -0
- qplot/tools/sample_statistics.py +111 -0
- qplot/tools/worker.py +2838 -0
- qplot/windows/__init__.py +9 -0
- qplot/windows/_colorbar.py +422 -0
- qplot/windows/_commands.py +523 -0
- qplot/windows/_config_persistence.py +123 -0
- qplot/windows/_database_actions.py +4933 -0
- qplot/windows/_dataset_handle.py +221 -0
- qplot/windows/_display_precision.py +48 -0
- qplot/windows/_dragdrop.py +70 -0
- qplot/windows/_export_paths.py +1167 -0
- qplot/windows/_help.py +210 -0
- qplot/windows/_mixed_appearance.py +303 -0
- qplot/windows/_mixed_plots.py +461 -0
- qplot/windows/_native_averaging.py +323 -0
- qplot/windows/_native_transforms.py +551 -0
- qplot/windows/_plot1d_snap.py +612 -0
- qplot/windows/_plot1d_traces.py +2236 -0
- qplot/windows/_plot2d_colorbar.py +1127 -0
- qplot/windows/_plot2d_colorbar_dialog.py +786 -0
- qplot/windows/_plot2d_layers.py +2291 -0
- qplot/windows/_plot2d_sweeps.py +712 -0
- qplot/windows/_plotWin.py +2082 -0
- qplot/windows/_plot_actions.py +2703 -0
- qplot/windows/_plot_annotation.py +186 -0
- qplot/windows/_plot_appearance.py +283 -0
- qplot/windows/_plot_axis_scaling.py +1521 -0
- qplot/windows/_plot_data_viewbox.py +94 -0
- qplot/windows/_plot_drop_axes.py +224 -0
- qplot/windows/_plot_export.py +1445 -0
- qplot/windows/_plot_feedback.py +112 -0
- qplot/windows/_plot_marquee.py +659 -0
- qplot/windows/_plot_refresh.py +950 -0
- qplot/windows/_plot_source_promotion.py +178 -0
- qplot/windows/_plot_state.py +237 -0
- qplot/windows/_plot_status.py +115 -0
- qplot/windows/_plot_tiling.py +35 -0
- qplot/windows/_plot_transform_labels.py +60 -0
- qplot/windows/_preferences.py +724 -0
- qplot/windows/_refresh_interval.py +172 -0
- qplot/windows/_run_controls.py +789 -0
- qplot/windows/_shortcuts.py +47 -0
- qplot/windows/_subplots/__init__.py +8 -0
- qplot/windows/_subplots/subplot1d.py +532 -0
- qplot/windows/_subplots/subplot2d.py +919 -0
- qplot/windows/_svg_export.py +209 -0
- qplot/windows/_trusted_derived_qt.py +1759 -0
- qplot/windows/_widgets/__init__.py +23 -0
- qplot/windows/_widgets/_run_formatting.py +387 -0
- qplot/windows/_widgets/details_tables.py +1119 -0
- qplot/windows/_widgets/dropbox.py +290 -0
- qplot/windows/_widgets/operations.py +460 -0
- qplot/windows/_widgets/preview.py +2997 -0
- qplot/windows/_widgets/run_list_items.py +488 -0
- qplot/windows/_widgets/toolbar.py +111 -0
- qplot/windows/_widgets/treeWidgets.py +2572 -0
- qplot/windows/_window_controls.py +414 -0
- qplot/windows/main.py +1632 -0
- qplot/windows/plot1d.py +313 -0
- qplot/windows/plot2d.py +1935 -0
- qplotter-1.6.0.dist-info/METADATA +322 -0
- qplotter-1.6.0.dist-info/RECORD +130 -0
- qplotter-1.6.0.dist-info/WHEEL +5 -0
- qplotter-1.6.0.dist-info/entry_points.txt +4 -0
- qplotter-1.6.0.dist-info/licenses/LICENSE +21 -0
- qplotter-1.6.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
"""Explicit plot data captured through the application's trusted broker.
|
|
2
|
+
|
|
3
|
+
Only a run's result table is staged, never database pages or unrelated runs.
|
|
4
|
+
The private spool lets the existing precision-preserving QCoDeS decoder and
|
|
5
|
+
bounded heatmap aggregator operate on one immutable acquisition prefix. No
|
|
6
|
+
source transaction survives a page, a BLOB chunk, or time spent processing it.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sqlite3
|
|
10
|
+
import tempfile
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any, cast
|
|
13
|
+
|
|
14
|
+
from qcodes.dataset.data_set_cache import DataSetCacheWithDBBackend
|
|
15
|
+
from qcodes.dataset.descriptions.versioning.converters import new_to_old
|
|
16
|
+
from qcodes.dataset.descriptions.versioning.serialization import from_json_to_current
|
|
17
|
+
from qcodes.dataset.sqlite.connection import AtomicConnection
|
|
18
|
+
|
|
19
|
+
from .trusted_live import TrustedLiveQueryError, TrustedQuery
|
|
20
|
+
|
|
21
|
+
PAGE_ROWS = 8192
|
|
22
|
+
BLOB_CHUNK_BYTES = 256 * 1024
|
|
23
|
+
RECORD_PROGRESS_UNITS = 1_000_000
|
|
24
|
+
MAX_PARAMETERS = 256
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def identifier(name):
|
|
28
|
+
return '"' + str(name).replace('"', '""') + '"'
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _report_array_progress(executor, phase, row_id, watermark, copied_bytes, record_bytes):
|
|
32
|
+
# Byte progress advances within this record's share of the captured range,
|
|
33
|
+
# instead of repeatedly resetting the bar to 0% for each stored array.
|
|
34
|
+
completed_units = (row_id - 1) * RECORD_PROGRESS_UNITS
|
|
35
|
+
if record_bytes:
|
|
36
|
+
completed_units += copied_bytes * RECORD_PROGRESS_UNITS // record_bytes
|
|
37
|
+
executor.report_progress(phase, completed_units, watermark * RECORD_PROGRESS_UNITS)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TrustedPlotDataset:
|
|
41
|
+
"""Connection-free run description and viewer-owned QCoDeS cache."""
|
|
42
|
+
|
|
43
|
+
def __init__(self, service, row):
|
|
44
|
+
(self.run_id, self.guid, self.table_name, description,
|
|
45
|
+
completed, self.name, self.exp_name, self.sample_name) = row
|
|
46
|
+
self.service = service
|
|
47
|
+
self.path_to_db = service.database_instance.logical_path
|
|
48
|
+
self.description_json = description
|
|
49
|
+
self.description = from_json_to_current(description)
|
|
50
|
+
self._parameters = list(new_to_old(self.description.interdeps).paramspecs)
|
|
51
|
+
if not 0 < len(self._parameters) <= MAX_PARAMETERS:
|
|
52
|
+
raise ValueError("Plot parameter count exceeds the supported bound.")
|
|
53
|
+
self._completed = bool(completed)
|
|
54
|
+
self.number_of_results = 0
|
|
55
|
+
# Only qPlot's cache accessors are used; no DataSet database methods.
|
|
56
|
+
self.cache = DataSetCacheWithDBBackend(cast(Any, self))
|
|
57
|
+
self.cache.prepare()
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def completed(self):
|
|
61
|
+
return self._completed
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def running(self):
|
|
65
|
+
return not self._completed
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def paramspecs(self):
|
|
69
|
+
return {parameter.name: parameter for parameter in self._parameters}
|
|
70
|
+
|
|
71
|
+
def get_parameters(self):
|
|
72
|
+
return list(self._parameters)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def plot_dataset(executor, service, guid):
|
|
76
|
+
rows = executor.query(
|
|
77
|
+
"SELECT r.run_id, r.guid, r.result_table_name, r.run_description, "
|
|
78
|
+
"r.is_completed, r.name, e.name, e.sample_name "
|
|
79
|
+
"FROM runs r JOIN experiments e ON r.exp_id=e.exp_id "
|
|
80
|
+
"WHERE r.guid=? LIMIT 2", (guid,),
|
|
81
|
+
).rows
|
|
82
|
+
if len(rows) != 1:
|
|
83
|
+
raise ValueError("The requested plot GUID does not identify one run.")
|
|
84
|
+
return TrustedPlotDataset(service, rows[0])
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class PlotPrefix:
|
|
88
|
+
"""Owned private storage, shared only after its writer has closed."""
|
|
89
|
+
|
|
90
|
+
def __init__(self):
|
|
91
|
+
self.directory = tempfile.TemporaryDirectory(prefix="qplot-plot-")
|
|
92
|
+
self.path = Path(self.directory.name) / "results.db"
|
|
93
|
+
self.row_count = 0
|
|
94
|
+
self.decode_bytes = 0
|
|
95
|
+
self.watermark = 0
|
|
96
|
+
self.disk_bytes = 0
|
|
97
|
+
|
|
98
|
+
def connect(self, *, decode_arrays=False, cancelled=lambda: False):
|
|
99
|
+
if decode_arrays:
|
|
100
|
+
from qcodes.dataset.sqlite import database as qcodes_database
|
|
101
|
+
|
|
102
|
+
from .readonly import _register_qcodes_sqlite_types
|
|
103
|
+
|
|
104
|
+
_register_qcodes_sqlite_types(qcodes_database)
|
|
105
|
+
conn = sqlite3.connect(
|
|
106
|
+
self.path.as_uri() + "?mode=ro&immutable=1", uri=True,
|
|
107
|
+
factory=AtomicConnection,
|
|
108
|
+
detect_types=sqlite3.PARSE_DECLTYPES if decode_arrays else 0,
|
|
109
|
+
)
|
|
110
|
+
conn.execute("PRAGMA query_only=ON")
|
|
111
|
+
conn.set_progress_handler(lambda: int(cancelled()), 1000)
|
|
112
|
+
return conn
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def plot_prefix(executor, dataset, heatmap_plan=None):
|
|
116
|
+
"""Capture a run in resumable steps, returning its immutable private spool.
|
|
117
|
+
|
|
118
|
+
Yields occur with no source transaction open, after each numeric page or
|
|
119
|
+
array chunk. Only the broker dispatcher may advance or close this iterator.
|
|
120
|
+
"""
|
|
121
|
+
table = identifier(dataset.table_name)
|
|
122
|
+
executor.report_progress("Checking plot data")
|
|
123
|
+
metadata, extent = executor.query_batch((
|
|
124
|
+
TrustedQuery(
|
|
125
|
+
"SELECT guid, result_table_name, run_description, is_completed "
|
|
126
|
+
"FROM runs WHERE run_id=?", (dataset.run_id,),
|
|
127
|
+
),
|
|
128
|
+
TrustedQuery(f"SELECT MAX(id) FROM {table}"),
|
|
129
|
+
))
|
|
130
|
+
expected = (dataset.guid, dataset.table_name, dataset.description_json)
|
|
131
|
+
if len(metadata.rows) != 1 or metadata.rows[0][:3] != expected:
|
|
132
|
+
raise TrustedLiveQueryError("The plot run identity changed.")
|
|
133
|
+
completed = bool(metadata.rows[0][3])
|
|
134
|
+
watermark = extent.rows[0][0] or 0
|
|
135
|
+
cache_key = (dataset.run_id, dataset.guid, dataset.description_json, heatmap_plan)
|
|
136
|
+
retained = dataset.service._plot_prefix_cache.get(cache_key)
|
|
137
|
+
if completed and retained is not None and retained.watermark == watermark:
|
|
138
|
+
return retained
|
|
139
|
+
specs = dataset.get_parameters()
|
|
140
|
+
names = [identifier(spec.name) for spec in specs]
|
|
141
|
+
if any(spec.type not in {"numeric", "array", "complex", "text"} for spec in specs):
|
|
142
|
+
raise ValueError("Unsupported plot storage type.")
|
|
143
|
+
if heatmap_plan is not None and watermark > heatmap_plan.full_limit:
|
|
144
|
+
plan = heatmap_plan
|
|
145
|
+
if any(dataset.paramspecs[name].type != "numeric" for name in (plan.x, plan.y, plan.z)):
|
|
146
|
+
raise ValueError("The numeric heatmap plan requires numeric parameters.")
|
|
147
|
+
count = 0
|
|
148
|
+
executor.report_progress("Checking plot size")
|
|
149
|
+
for start in range(0, watermark, 65_536):
|
|
150
|
+
count += executor.query(
|
|
151
|
+
f"SELECT COUNT(*) FROM {table} WHERE id>? AND id<=? "
|
|
152
|
+
f"AND {identifier(plan.z)} IS NOT NULL",
|
|
153
|
+
(start, min(start + 65_536, watermark)),
|
|
154
|
+
).rows[0][0]
|
|
155
|
+
yield
|
|
156
|
+
if count > plan.full_limit:
|
|
157
|
+
break
|
|
158
|
+
if count > plan.full_limit:
|
|
159
|
+
from .trusted_heatmap import PlotOperationLimitError, numeric_heatmap
|
|
160
|
+
if plan.operations:
|
|
161
|
+
raise PlotOperationLimitError(
|
|
162
|
+
"This heatmap exceeds max_full_heatmap_points. "
|
|
163
|
+
"Disable operations or increase max_full_heatmap_points.")
|
|
164
|
+
heatmap = yield from numeric_heatmap(executor, dataset, plan, watermark, completed)
|
|
165
|
+
final = executor.query(
|
|
166
|
+
"SELECT guid, result_table_name, run_description FROM runs WHERE run_id=?",
|
|
167
|
+
(dataset.run_id,),
|
|
168
|
+
).rows
|
|
169
|
+
if final != (expected,):
|
|
170
|
+
raise TrustedLiveQueryError("The plot run identity changed during capture.")
|
|
171
|
+
heatmap.watermark = watermark
|
|
172
|
+
if completed and heatmap.disk_bytes <= 512 * 1024 * 1024:
|
|
173
|
+
_cache_prefix(dataset.service, cache_key, heatmap)
|
|
174
|
+
return heatmap
|
|
175
|
+
prefix = PlotPrefix()
|
|
176
|
+
prefix.watermark = watermark
|
|
177
|
+
executor.report_progress("Reading plot data", 0, watermark)
|
|
178
|
+
# This is a newly created, private database. It contains no source metadata
|
|
179
|
+
# besides the completion observation needed by the existing cache loader.
|
|
180
|
+
try:
|
|
181
|
+
with sqlite3.connect(prefix.path) as target:
|
|
182
|
+
def interrupted():
|
|
183
|
+
try:
|
|
184
|
+
executor.check_cancelled()
|
|
185
|
+
return 0
|
|
186
|
+
except (InterruptedError, TimeoutError):
|
|
187
|
+
return 1
|
|
188
|
+
target.set_progress_handler(interrupted, 1000)
|
|
189
|
+
target.execute("PRAGMA cache_size=-2048")
|
|
190
|
+
target.execute("CREATE TABLE runs(run_id INTEGER PRIMARY KEY, is_completed INTEGER)")
|
|
191
|
+
target.execute("INSERT INTO runs VALUES (?, ?)", (dataset.run_id, completed))
|
|
192
|
+
columns = ", ".join(f"{name} {spec.type}" for name, spec in zip(names, specs, strict=True))
|
|
193
|
+
target.execute(f"CREATE TABLE {table}(id INTEGER PRIMARY KEY, {columns})")
|
|
194
|
+
# BLOB lengths travel separately; even a multi-gigabyte array never
|
|
195
|
+
# becomes a scalar in the reader, IPC, or this process.
|
|
196
|
+
values_sql = ", ".join(
|
|
197
|
+
f"CASE WHEN typeof({name})='blob' THEN NULL ELSE {name} END, "
|
|
198
|
+
f"CASE WHEN typeof({name})='blob' THEN length({name}) ELSE NULL END"
|
|
199
|
+
for name in names
|
|
200
|
+
)
|
|
201
|
+
last_id = 0
|
|
202
|
+
page_size = min(PAGE_ROWS, max(1, 65536 // (2 * len(names))))
|
|
203
|
+
while last_id < watermark:
|
|
204
|
+
rows = executor.query(
|
|
205
|
+
f"SELECT id, {values_sql} FROM {table} "
|
|
206
|
+
"WHERE id>? AND id<=? ORDER BY id LIMIT ?",
|
|
207
|
+
(last_id, watermark, page_size),
|
|
208
|
+
).rows
|
|
209
|
+
if not rows:
|
|
210
|
+
break
|
|
211
|
+
for row in rows:
|
|
212
|
+
executor.check_cancelled()
|
|
213
|
+
row_id = row[0]
|
|
214
|
+
expressions = ["?"]
|
|
215
|
+
bindings = [row_id]
|
|
216
|
+
for index in range(len(names)):
|
|
217
|
+
value, size = row[1 + 2 * index:3 + 2 * index]
|
|
218
|
+
expressions.append("?" if size is None else "zeroblob(?)")
|
|
219
|
+
bindings.append(value if size is None else size)
|
|
220
|
+
prefix.decode_bytes += 128 + (size or 0) + (
|
|
221
|
+
4 * len(value) if isinstance(value, str) else 0)
|
|
222
|
+
target.execute(
|
|
223
|
+
f"INSERT INTO {table} VALUES ({','.join(expressions)})", bindings,
|
|
224
|
+
)
|
|
225
|
+
record_bytes = sum(row[2 + 2 * index] or 0 for index in range(len(names)))
|
|
226
|
+
copied_bytes = 0
|
|
227
|
+
|
|
228
|
+
for index in range(len(names)):
|
|
229
|
+
size = row[2 + 2 * index]
|
|
230
|
+
if size is None:
|
|
231
|
+
continue
|
|
232
|
+
with target.blobopen(dataset.table_name, specs[index].name, row_id) as blob:
|
|
233
|
+
array_phase = f"Reading array {specs[index].name} (record {prefix.row_count + 1:,})"
|
|
234
|
+
_report_array_progress(
|
|
235
|
+
executor, array_phase, row_id, watermark, copied_bytes, record_bytes,
|
|
236
|
+
)
|
|
237
|
+
for offset in range(0, size, BLOB_CHUNK_BYTES):
|
|
238
|
+
chunk = executor.query(
|
|
239
|
+
"SELECT qplot_read_blob(?, ?, ?, ?, ?)",
|
|
240
|
+
(dataset.table_name, specs[index].name, row_id,
|
|
241
|
+
offset, min(BLOB_CHUNK_BYTES, size - offset)),
|
|
242
|
+
).rows
|
|
243
|
+
if len(chunk) != 1 or len(chunk[0][0]) != min(BLOB_CHUNK_BYTES, size - offset):
|
|
244
|
+
raise TrustedLiveQueryError("A captured array record changed.")
|
|
245
|
+
blob.write(chunk[0][0])
|
|
246
|
+
copied_bytes += len(chunk[0][0])
|
|
247
|
+
_report_array_progress(
|
|
248
|
+
executor, array_phase, row_id, watermark, copied_bytes, record_bytes,
|
|
249
|
+
)
|
|
250
|
+
yield
|
|
251
|
+
prefix.row_count += 1
|
|
252
|
+
last_id = rows[-1][0]
|
|
253
|
+
target.commit()
|
|
254
|
+
executor.report_progress("Reading plot data", last_id, watermark)
|
|
255
|
+
if last_id < watermark:
|
|
256
|
+
yield
|
|
257
|
+
executor.report_progress("Validating plot data")
|
|
258
|
+
final = executor.query(
|
|
259
|
+
"SELECT guid, result_table_name, run_description FROM runs WHERE run_id=?",
|
|
260
|
+
(dataset.run_id,),
|
|
261
|
+
).rows
|
|
262
|
+
if final != (expected,):
|
|
263
|
+
raise TrustedLiveQueryError("The plot run identity changed during capture.")
|
|
264
|
+
# Context managers commit but do not close sqlite3 connections.
|
|
265
|
+
target.close()
|
|
266
|
+
prefix.disk_bytes = prefix.path.stat().st_size
|
|
267
|
+
if completed and prefix.disk_bytes <= 512 * 1024 * 1024:
|
|
268
|
+
_cache_prefix(dataset.service, cache_key, prefix)
|
|
269
|
+
return prefix
|
|
270
|
+
except BaseException:
|
|
271
|
+
if "target" in locals():
|
|
272
|
+
target.close()
|
|
273
|
+
prefix.directory.cleanup()
|
|
274
|
+
raise
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _cache_prefix(service, key, prefix):
|
|
278
|
+
cache = service._plot_prefix_cache
|
|
279
|
+
cache.pop(key, None)
|
|
280
|
+
while cache and (len(cache) >= 8 or
|
|
281
|
+
sum(item.disk_bytes for item in cache.values()) + prefix.disk_bytes > 512 * 1024 * 1024):
|
|
282
|
+
cache.pop(next(iter(cache)))
|
|
283
|
+
cache[key] = prefix
|