qplotter 1.6.0b3__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.
Files changed (119) hide show
  1. qplot/__init__.py +46 -0
  2. qplot/__main__.py +232 -0
  3. qplot/_auxiliary_paths.py +63 -0
  4. qplot/_metadata.py +12 -0
  5. qplot/_repair.py +14 -0
  6. qplot/_shutdown_supervisor.py +4049 -0
  7. qplot/_version.py +12 -0
  8. qplot/_windows_shutdown_job.py +1166 -0
  9. qplot/configuration/__init__.py +3 -0
  10. qplot/configuration/config.py +573 -0
  11. qplot/configuration/config_schema.json +319 -0
  12. qplot/configuration/scripts.py +255 -0
  13. qplot/configuration/themes/__init__.py +13 -0
  14. qplot/configuration/themes/_base.py +579 -0
  15. qplot/configuration/themes/blank.py +17 -0
  16. qplot/configuration/themes/dark.py +54 -0
  17. qplot/configuration/themes/light.py +54 -0
  18. qplot/datahandling/LoadFromDB.py +224 -0
  19. qplot/datahandling/__init__.py +47 -0
  20. qplot/datahandling/_trusted_live_helper.py +812 -0
  21. qplot/datahandling/_trusted_live_protocol.py +1745 -0
  22. qplot/datahandling/database.py +2330 -0
  23. qplot/datahandling/dimensions.py +43 -0
  24. qplot/datahandling/file_identity.py +461 -0
  25. qplot/datahandling/parameter_data.py +149 -0
  26. qplot/datahandling/plot_progress.py +17 -0
  27. qplot/datahandling/qcodes_cache.py +277 -0
  28. qplot/datahandling/qcodes_compat.py +14 -0
  29. qplot/datahandling/readSQL.py +2442 -0
  30. qplot/datahandling/readonly.py +2561 -0
  31. qplot/datahandling/source_activity.py +21 -0
  32. qplot/datahandling/trusted_array_preview.py +127 -0
  33. qplot/datahandling/trusted_derived_cache.py +1605 -0
  34. qplot/datahandling/trusted_derived_rendering.py +868 -0
  35. qplot/datahandling/trusted_heatmap.py +241 -0
  36. qplot/datahandling/trusted_live.py +2808 -0
  37. qplot/datahandling/trusted_live_queries.py +5907 -0
  38. qplot/datahandling/trusted_live_service.py +1894 -0
  39. qplot/datahandling/trusted_live_supervisor.py +2161 -0
  40. qplot/datahandling/trusted_plot.py +283 -0
  41. qplot/datahandling/trusted_presentation.py +1065 -0
  42. qplot/datahandling/trusted_snapshot.py +1132 -0
  43. qplot/datahandling/trusted_work_coordinator.py +2157 -0
  44. qplot/datahandling/trusted_work_scheduler.py +1204 -0
  45. qplot/diagnostics.py +241 -0
  46. qplot/resources/testdata/qplot_test_db_01_10mb.csv +11 -0
  47. qplot/resources/testdata/qplot_test_db_02_25mb.csv +14 -0
  48. qplot/resources/testdata/qplot_test_db_03_50mb.csv +17 -0
  49. qplot/resources/testdata/qplot_test_db_04_100mb.csv +20 -0
  50. qplot/resources/testdata/qplot_test_db_05_250mb.csv +23 -0
  51. qplot/resources/testdata/qplot_test_db_06_500mb.csv +26 -0
  52. qplot/resources/testdata/qplot_test_db_07_1gb.csv +29 -0
  53. qplot/resources/testdata/qplot_test_db_08_5gb.csv +32 -0
  54. qplot/resources/testdata/qplot_test_db_09_10gb.csv +35 -0
  55. qplot/resources/testdata/qplot_test_db_10_30gb.csv +38 -0
  56. qplot/testdata.py +1733 -0
  57. qplot/tools/__init__.py +13 -0
  58. qplot/tools/finite_means.py +114 -0
  59. qplot/tools/general.py +134 -0
  60. qplot/tools/heatmap_geometry.py +381 -0
  61. qplot/tools/operation_registry.py +179 -0
  62. qplot/tools/plot_tools.py +625 -0
  63. qplot/tools/sample_statistics.py +56 -0
  64. qplot/tools/worker.py +2787 -0
  65. qplot/windows/__init__.py +9 -0
  66. qplot/windows/_colorbar.py +422 -0
  67. qplot/windows/_commands.py +523 -0
  68. qplot/windows/_config_persistence.py +123 -0
  69. qplot/windows/_database_actions.py +4904 -0
  70. qplot/windows/_dataset_handle.py +221 -0
  71. qplot/windows/_dragdrop.py +71 -0
  72. qplot/windows/_export_paths.py +1167 -0
  73. qplot/windows/_help.py +169 -0
  74. qplot/windows/_native_averaging.py +173 -0
  75. qplot/windows/_native_transforms.py +389 -0
  76. qplot/windows/_plot1d_snap.py +608 -0
  77. qplot/windows/_plot1d_traces.py +2183 -0
  78. qplot/windows/_plot2d_colorbar.py +1126 -0
  79. qplot/windows/_plot2d_colorbar_dialog.py +786 -0
  80. qplot/windows/_plot2d_layers.py +2197 -0
  81. qplot/windows/_plot2d_sweeps.py +712 -0
  82. qplot/windows/_plotWin.py +1993 -0
  83. qplot/windows/_plot_actions.py +2695 -0
  84. qplot/windows/_plot_appearance.py +276 -0
  85. qplot/windows/_plot_axis_scaling.py +1485 -0
  86. qplot/windows/_plot_export.py +1445 -0
  87. qplot/windows/_plot_feedback.py +110 -0
  88. qplot/windows/_plot_marquee.py +656 -0
  89. qplot/windows/_plot_refresh.py +942 -0
  90. qplot/windows/_plot_state.py +239 -0
  91. qplot/windows/_plot_transform_labels.py +60 -0
  92. qplot/windows/_preferences.py +635 -0
  93. qplot/windows/_refresh_interval.py +70 -0
  94. qplot/windows/_run_controls.py +828 -0
  95. qplot/windows/_shortcuts.py +47 -0
  96. qplot/windows/_subplots/__init__.py +8 -0
  97. qplot/windows/_subplots/subplot1d.py +531 -0
  98. qplot/windows/_subplots/subplot2d.py +918 -0
  99. qplot/windows/_svg_export.py +209 -0
  100. qplot/windows/_trusted_derived_qt.py +1759 -0
  101. qplot/windows/_widgets/__init__.py +23 -0
  102. qplot/windows/_widgets/_run_formatting.py +387 -0
  103. qplot/windows/_widgets/details_tables.py +1119 -0
  104. qplot/windows/_widgets/dropbox.py +290 -0
  105. qplot/windows/_widgets/operations.py +460 -0
  106. qplot/windows/_widgets/preview.py +2997 -0
  107. qplot/windows/_widgets/run_list_items.py +488 -0
  108. qplot/windows/_widgets/toolbar.py +111 -0
  109. qplot/windows/_widgets/treeWidgets.py +2572 -0
  110. qplot/windows/_window_controls.py +413 -0
  111. qplot/windows/main.py +1635 -0
  112. qplot/windows/plot1d.py +311 -0
  113. qplot/windows/plot2d.py +1901 -0
  114. qplotter-1.6.0b3.dist-info/METADATA +318 -0
  115. qplotter-1.6.0b3.dist-info/RECORD +119 -0
  116. qplotter-1.6.0b3.dist-info/WHEEL +5 -0
  117. qplotter-1.6.0b3.dist-info/entry_points.txt +4 -0
  118. qplotter-1.6.0b3.dist-info/licenses/LICENSE +21 -0
  119. qplotter-1.6.0b3.dist-info/top_level.txt +1 -0
qplot/__init__.py ADDED
@@ -0,0 +1,46 @@
1
+ from ._metadata import package_version
2
+
3
+ __version__ = package_version()
4
+
5
+ __all__ = [
6
+ "datahandling",
7
+ "windows",
8
+ "tools",
9
+ "run",
10
+ "config",
11
+ "__version__",
12
+ ]
13
+
14
+
15
+ def run(*args, **kwargs):
16
+ """
17
+ Start qPlot through its dedicated public-API launcher.
18
+
19
+ Plain package import still avoids importing GUI modules. Passing
20
+ ``return_objects=True`` remains the explicit caller-owned exception.
21
+
22
+ """
23
+ from .__main__ import run_public as _run
24
+
25
+ return _run(*args, **kwargs)
26
+
27
+
28
+ def __getattr__(name):
29
+ if name == "config":
30
+ from qplot.configuration.config import config
31
+
32
+ globals()[name] = config
33
+ return config
34
+
35
+ if name in {"datahandling", "tools", "windows"}:
36
+ import importlib
37
+
38
+ module = importlib.import_module(f"qplot.{name}")
39
+ globals()[name] = module
40
+ return module
41
+
42
+ raise AttributeError(f"module 'qplot' has no attribute {name!r}")
43
+
44
+
45
+ def __dir__():
46
+ return sorted(set(globals()) | set(__all__))
qplot/__main__.py ADDED
@@ -0,0 +1,232 @@
1
+ import sys
2
+
3
+ from qplot._metadata import package_version
4
+ from qplot.diagnostics import (
5
+ configure_logging,
6
+ install_excepthook,
7
+ log_event,
8
+ log_exception,
9
+ )
10
+
11
+ QT_OPTIONS_WITH_VALUES = {
12
+ "-display",
13
+ "-font",
14
+ "-geometry",
15
+ "-name",
16
+ "-platform",
17
+ "-platformpluginpath",
18
+ "-platformtheme",
19
+ "-plugin",
20
+ "-qwindowgeometry",
21
+ "-qwindowicon",
22
+ "-qwindowtitle",
23
+ "-session",
24
+ "-style",
25
+ "-stylesheet",
26
+ "-title",
27
+ "-visual",
28
+ }
29
+
30
+
31
+ def _database_path_from_arguments(args):
32
+ """
33
+ Return the first database path passed on the command line.
34
+
35
+ File managers pass the double-clicked file as a plain positional argument.
36
+ Qt options are ignored here so they can still be handled by QApplication.
37
+
38
+ """
39
+ skip_next = False
40
+ positional_only = False
41
+ for arg in args:
42
+ if skip_next:
43
+ skip_next = False
44
+ continue
45
+ if arg == "--":
46
+ positional_only = True
47
+ continue
48
+ if not positional_only and arg in QT_OPTIONS_WITH_VALUES:
49
+ skip_next = True
50
+ continue
51
+ if not positional_only and arg.startswith("-"):
52
+ continue
53
+ return arg
54
+
55
+ return None
56
+
57
+
58
+ def _configure_application_identity(app):
59
+ """
60
+ Sets the application name used by native desktop menus.
61
+
62
+ """
63
+ app.setApplicationName("qPlot")
64
+ if hasattr(app, "setApplicationDisplayName"):
65
+ app.setApplicationDisplayName("qPlot")
66
+
67
+
68
+ def _finalize_confirmed_process_shutdown(
69
+ app,
70
+ window,
71
+ process_fail_safe,
72
+ *,
73
+ retain_objects,
74
+ ):
75
+ """Destroy quiescent Qt ownership while launcher containment stays armed."""
76
+
77
+ from PyQt6 import sip
78
+
79
+ from qplot.windows import MainWindow
80
+
81
+ if not process_fail_safe.armed:
82
+ return
83
+
84
+ deadline_exhausted = bool(getattr(window, "_shutdown_deadline_exhausted", False))
85
+ quiescent = False
86
+ if not deadline_exhausted:
87
+ try:
88
+ quiescent = not MainWindow._shutdown_background_work_active(window)
89
+ except BaseException:
90
+ quiescent = False
91
+
92
+ if retain_objects:
93
+ if quiescent:
94
+ process_fail_safe.disarm()
95
+ return
96
+ process_fail_safe.wait_for_forced_exit()
97
+ return
98
+
99
+ # Never enter a QThreadPool-owning destructor after the deadline or while
100
+ # the final resource scan is non-quiescent. sip.delete() may hold this
101
+ # entry-point thread indefinitely, which would otherwise make its
102
+ # hard-deadline os._exit fallback unreachable if the launcher failed.
103
+ if deadline_exhausted or not quiescent:
104
+ process_fail_safe.wait_for_forced_exit()
105
+ return
106
+
107
+ # The launcher remains armed through both destructors in the quiescent path.
108
+ if not process_fail_safe.watchdog_operational():
109
+ process_fail_safe.wait_for_forced_exit()
110
+ return
111
+ try:
112
+ sip.delete(window)
113
+ if not process_fail_safe.watchdog_operational():
114
+ process_fail_safe.wait_for_forced_exit()
115
+ return
116
+ sip.delete(app)
117
+ except BaseException:
118
+ process_fail_safe.wait_for_forced_exit()
119
+ raise
120
+ process_fail_safe.disarm()
121
+
122
+
123
+ def _run_gui(
124
+ return_objects=False,
125
+ database_path=None,
126
+ *,
127
+ shutdown_supervisor_client=None,
128
+ shutdown_supervisor_diagnostic=None,
129
+ ):
130
+ """Run Qt in this process, optionally with an authenticated launcher client."""
131
+
132
+ from PyQt6 import QtWidgets as qtw
133
+
134
+ from qplot.windows import MainWindow
135
+ from qplot.windows.main import _ProcessShutdownFailSafe
136
+
137
+ configure_logging()
138
+ install_excepthook()
139
+ log_event("Starting qPlot %s", package_version())
140
+ print("Initialising GUI, this may take a few seconds.\n")
141
+
142
+ try:
143
+ app = qtw.QApplication(sys.argv)
144
+ _configure_application_identity(app)
145
+ if database_path is None:
146
+ database_path = _database_path_from_arguments(sys.argv[1:])
147
+ w = MainWindow(startup_database_path=database_path)
148
+ process_fail_safe = None
149
+ if hasattr(w, "_shutdown_process_fail_safe"):
150
+ process_fail_safe = _ProcessShutdownFailSafe(
151
+ shutdown_supervisor_client,
152
+ startup_diagnostic=shutdown_supervisor_diagnostic,
153
+ )
154
+ w._shutdown_process_fail_safe = process_fail_safe
155
+ exit_code = app.exec()
156
+ except Exception as err:
157
+ log_exception("qPlot startup failed", err)
158
+ raise
159
+
160
+ log_event("qPlot event loop exited with code %s", exit_code)
161
+
162
+ if process_fail_safe is not None:
163
+ _finalize_confirmed_process_shutdown(
164
+ app,
165
+ w,
166
+ process_fail_safe,
167
+ retain_objects=bool(return_objects),
168
+ )
169
+
170
+ if return_objects:
171
+ return app, w
172
+ return exit_code
173
+
174
+
175
+ def run(return_objects=False, database_path=None):
176
+ """
177
+ Entry point for opening the qplot app.
178
+
179
+ Parameters
180
+ ----------
181
+ return_objects : bool, optional
182
+ If true, returns the QApplication and MainWindow after the event loop
183
+ exits. This deliberately runs in the caller's process, which owns the
184
+ returned objects and their cleanup; it does not acquire the default
185
+ launcher's process-tree containment or hard-deadline guarantee. The
186
+ default is false so the command-line entry point uses that launcher and
187
+ exits quietly and successfully.
188
+ database_path : str, optional
189
+ QCoDeS database path to load after the main window opens. When omitted,
190
+ qPlot uses the first positional path passed on the command line, if any.
191
+
192
+ Returns
193
+ -------
194
+ tuple[PyQt6.QtWidgets.QApplication, qplot.windows.main.MainWindow] | int
195
+ Application objects when return_objects is true; otherwise the Qt
196
+ event-loop exit status.
197
+
198
+ """
199
+ if return_objects:
200
+ return _run_gui(return_objects=True, database_path=database_path)
201
+
202
+ from qplot._shutdown_supervisor import launch_gui
203
+
204
+ return launch_gui(original_argv=sys.argv, database_path=database_path)
205
+
206
+
207
+ def run_public(return_objects=False, database_path=None):
208
+ """Run qPlot for library callers without containing the caller process.
209
+
210
+ The default path delegates GUI ownership to a dedicated launcher process.
211
+ It returns the GUI status normally; on POSIX, termination by signal is
212
+ represented non-destructively as ``-signal_number``. Unlike the command
213
+ line entry point, this function never reproduces that signal or hard-exits
214
+ the Python process that called :func:`qplot.run`.
215
+
216
+ Caller control-flow exceptions are preserved while the dedicated launcher
217
+ terminates and reaps its complete tree, then re-raised unchanged after the
218
+ authenticated outcome and launcher EOF have been observed.
219
+
220
+ ``return_objects=True`` intentionally retains the historical in-process,
221
+ caller-owned behavior and does not provide the hard-deadline guarantee.
222
+ """
223
+ if return_objects:
224
+ return _run_gui(return_objects=True, database_path=database_path)
225
+
226
+ from qplot._shutdown_supervisor import launch_gui_for_api
227
+
228
+ return launch_gui_for_api(original_argv=sys.argv, database_path=database_path)
229
+
230
+
231
+ if __name__ == "__main__":
232
+ raise SystemExit(run())
@@ -0,0 +1,63 @@
1
+ """Keep settings and diagnostics away from existing SQLite artifacts."""
2
+
3
+ import os
4
+ import stat
5
+
6
+ _SQLITE_HEADER = b"SQLite format 3\x00"
7
+ _SQLITE_HEADERS = (
8
+ _SQLITE_HEADER,
9
+ b"\x37\x7f\x06\x82", # WAL, little-endian checksums
10
+ b"\x37\x7f\x06\x83", # WAL, big-endian checksums
11
+ b"\xd9\xd5\x05\xf9\x20\xa1\x63\xd7", # rollback journal
12
+ )
13
+ _SIDECAR_SUFFIXES = ("-wal", "-journal", "-shm")
14
+
15
+
16
+ def _signature(file_stat):
17
+ return tuple(int(getattr(file_stat, field)) for field in (
18
+ "st_dev", "st_ino", "st_mode", "st_nlink", "st_size",
19
+ "st_mtime_ns", "st_ctime_ns",
20
+ ))
21
+
22
+
23
+ def _read_header(filename):
24
+ """Inspect a bounded regular file without SQLite or writable handles."""
25
+ try:
26
+ before = os.lstat(filename)
27
+ except FileNotFoundError:
28
+ return b""
29
+ if not stat.S_ISREG(before.st_mode):
30
+ raise OSError("The auxiliary destination is not a regular file.")
31
+ flags = os.O_RDONLY | getattr(os, "O_BINARY", 0)
32
+ flags |= getattr(os, "O_NONBLOCK", 0) | getattr(os, "O_NOFOLLOW", 0)
33
+ flags |= getattr(os, "O_CLOEXEC", 0)
34
+ descriptor = os.open(filename, flags)
35
+ try:
36
+ opened = os.fstat(descriptor)
37
+ # Windows lstat and fstat expose different ctime definitions.
38
+ common_fields = 6 if os.name == "nt" else 7
39
+ if _signature(before)[:common_fields] != _signature(opened)[:common_fields]:
40
+ raise OSError("The auxiliary destination changed during inspection.")
41
+ header = os.read(descriptor, len(_SQLITE_HEADER))
42
+ if _signature(os.fstat(descriptor)) != _signature(opened):
43
+ raise OSError("The auxiliary destination changed during inspection.")
44
+ finally:
45
+ os.close(descriptor)
46
+ if _signature(os.lstat(filename)) != _signature(before):
47
+ raise OSError("The auxiliary destination changed during inspection.")
48
+ return header
49
+
50
+
51
+ def ensure_safe_auxiliary_path(filename):
52
+ """Reject database files and reserved sidecars before any auxiliary write.
53
+
54
+ These fixed application paths can contain measurement data even when the
55
+ database has never been selected in qPlot. Unreadable files fail closed.
56
+ """
57
+ filename = os.path.abspath(os.fspath(filename))
58
+ if _read_header(filename).startswith(_SQLITE_HEADERS):
59
+ raise OSError("The auxiliary destination contains a SQLite database or journal.")
60
+ for suffix in _SIDECAR_SUFFIXES:
61
+ if filename.casefold().endswith(suffix):
62
+ if _read_header(filename[:-len(suffix)]).startswith(_SQLITE_HEADER):
63
+ raise OSError("The auxiliary destination is reserved for a SQLite sidecar.")
qplot/_metadata.py ADDED
@@ -0,0 +1,12 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+
4
+ def package_version():
5
+ """
6
+ Return the installed qPlot package version.
7
+
8
+ """
9
+ try:
10
+ return version("qplotter")
11
+ except PackageNotFoundError:
12
+ return "0+unknown"
qplot/_repair.py ADDED
@@ -0,0 +1,14 @@
1
+ from os.path import isfile
2
+
3
+ from qcodes.dataset.sqlite.database import get_DB_location
4
+
5
+ from qplot.datahandling.readonly import qcodes_read_only_connection
6
+
7
+
8
+ def repair():
9
+ """
10
+ Attempts to remove SQL lock that can happens on crashes while in IDE
11
+
12
+ """
13
+ if isfile(get_DB_location()): # close conn is already open by mistake
14
+ qcodes_read_only_connection(get_DB_location()).close()