pymodaq 3.6.12__py3-none-any.whl → 4.0.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 pymodaq might be problematic. Click here for more details.

Files changed (233) hide show
  1. pymodaq/__init__.py +13 -6
  2. pymodaq/control_modules/__init__.py +0 -7
  3. pymodaq/control_modules/daq_move.py +965 -2
  4. pymodaq/control_modules/daq_move_ui.py +319 -0
  5. pymodaq/control_modules/daq_viewer.py +1573 -3
  6. pymodaq/control_modules/daq_viewer_ui.py +393 -0
  7. pymodaq/control_modules/mocks.py +51 -0
  8. pymodaq/control_modules/move_utility_classes.py +709 -8
  9. pymodaq/control_modules/utils.py +256 -0
  10. pymodaq/control_modules/viewer_utility_classes.py +663 -6
  11. pymodaq/daq_utils.py +89 -0
  12. pymodaq/dashboard.py +91 -72
  13. pymodaq/examples/custom_app.py +12 -11
  14. pymodaq/examples/custom_viewer.py +10 -10
  15. pymodaq/examples/function_plotter.py +16 -13
  16. pymodaq/examples/nonlinearscanner.py +8 -6
  17. pymodaq/examples/parameter_ex.py +7 -7
  18. pymodaq/examples/preset_MockCamera.xml +1 -0
  19. pymodaq/extensions/__init__.py +16 -0
  20. pymodaq/extensions/console.py +76 -0
  21. pymodaq/{daq_logger.py → extensions/daq_logger.py} +115 -65
  22. pymodaq/extensions/daq_scan.py +1339 -0
  23. pymodaq/extensions/daq_scan_ui.py +240 -0
  24. pymodaq/extensions/h5browser.py +23 -0
  25. pymodaq/{pid → extensions/pid}/__init__.py +4 -2
  26. pymodaq/{pid → extensions/pid}/daq_move_PID.py +2 -2
  27. pymodaq/{pid → extensions/pid}/pid_controller.py +48 -36
  28. pymodaq/{pid → extensions/pid}/utils.py +52 -6
  29. pymodaq/extensions/utils.py +40 -0
  30. pymodaq/post_treatment/__init__.py +6 -0
  31. pymodaq/{daq_analysis → post_treatment/daq_analysis}/daq_analysis_main.py +17 -17
  32. pymodaq/{daq_measurement → post_treatment/daq_measurement}/daq_measurement_main.py +8 -14
  33. pymodaq/post_treatment/load_and_plot.py +219 -0
  34. pymodaq/post_treatment/process_to_scalar.py +263 -0
  35. pymodaq/resources/QtDesigner_Ressources/Icon_Library/run_all.png +0 -0
  36. pymodaq/resources/QtDesigner_Ressources/Icon_Library/stop_all.png +0 -0
  37. pymodaq/resources/QtDesigner_Ressources/QtDesigner_ressources.bat +1 -1
  38. pymodaq/resources/QtDesigner_Ressources/QtDesigner_ressources.qrc +1 -0
  39. pymodaq/resources/QtDesigner_Ressources/QtDesigner_ressources_rc.py +109784 -109173
  40. pymodaq/resources/QtDesigner_Ressources/icons.svg +142 -0
  41. pymodaq/resources/VERSION +1 -1
  42. pymodaq/resources/config_template.toml +32 -13
  43. pymodaq/resources/preset_default.xml +1 -1
  44. pymodaq/{daq_utils → utils}/Tuto innosetup/script_full_setup.iss +1 -1
  45. pymodaq/utils/__init__.py +0 -29
  46. pymodaq/utils/abstract/__init__.py +48 -0
  47. pymodaq/{daq_utils → utils}/abstract/logger.py +7 -3
  48. pymodaq/utils/array_manipulation.py +379 -8
  49. pymodaq/{daq_utils → utils}/calibration_camera.py +6 -6
  50. pymodaq/{daq_utils → utils}/chrono_timer.py +1 -1
  51. pymodaq/utils/config.py +448 -0
  52. pymodaq/utils/conftests.py +5 -0
  53. pymodaq/utils/daq_utils.py +828 -8
  54. pymodaq/utils/data.py +1873 -7
  55. pymodaq/{daq_utils → utils}/db/db_logger/db_logger.py +86 -47
  56. pymodaq/{daq_utils → utils}/db/db_logger/db_logger_models.py +31 -10
  57. pymodaq/{daq_utils → utils}/enums.py +12 -7
  58. pymodaq/utils/exceptions.py +37 -0
  59. pymodaq/utils/factory.py +82 -0
  60. pymodaq/{daq_utils → utils}/gui_utils/__init__.py +1 -1
  61. pymodaq/utils/gui_utils/custom_app.py +129 -0
  62. pymodaq/utils/gui_utils/file_io.py +66 -0
  63. pymodaq/{daq_utils → utils}/gui_utils/layout.py +2 -2
  64. pymodaq/{daq_utils → utils}/gui_utils/utils.py +13 -3
  65. pymodaq/{daq_utils → utils}/gui_utils/widgets/__init__.py +2 -2
  66. pymodaq/utils/gui_utils/widgets/label.py +24 -0
  67. pymodaq/{daq_utils → utils}/gui_utils/widgets/lcd.py +12 -7
  68. pymodaq/{daq_utils → utils}/gui_utils/widgets/push.py +66 -2
  69. pymodaq/{daq_utils → utils}/gui_utils/widgets/qled.py +6 -4
  70. pymodaq/utils/gui_utils/widgets/spinbox.py +24 -0
  71. pymodaq/{daq_utils → utils}/gui_utils/widgets/table.py +2 -2
  72. pymodaq/utils/h5modules/__init__.py +1 -0
  73. pymodaq/{daq_utils/h5backend.py → utils/h5modules/backends.py} +200 -112
  74. pymodaq/utils/h5modules/browsing.py +683 -0
  75. pymodaq/utils/h5modules/data_saving.py +839 -0
  76. pymodaq/utils/h5modules/h5logging.py +110 -0
  77. pymodaq/utils/h5modules/module_saving.py +350 -0
  78. pymodaq/utils/h5modules/saving.py +914 -0
  79. pymodaq/utils/h5modules/utils.py +85 -0
  80. pymodaq/utils/logger.py +64 -6
  81. pymodaq/utils/managers/action_manager.py +460 -0
  82. pymodaq/{daq_utils → utils}/managers/batchscan_manager.py +144 -112
  83. pymodaq/{daq_utils → utils}/managers/modules_manager.py +188 -114
  84. pymodaq/{daq_utils → utils}/managers/overshoot_manager.py +3 -3
  85. pymodaq/utils/managers/parameter_manager.py +110 -0
  86. pymodaq/{daq_utils → utils}/managers/preset_manager.py +17 -13
  87. pymodaq/{daq_utils → utils}/managers/preset_manager_utils.py +8 -7
  88. pymodaq/{daq_utils → utils}/managers/remote_manager.py +7 -6
  89. pymodaq/{daq_utils → utils}/managers/roi_manager.py +148 -57
  90. pymodaq/utils/math_utils.py +546 -10
  91. pymodaq/{daq_utils → utils}/messenger.py +5 -1
  92. pymodaq/utils/parameter/__init__.py +2 -15
  93. pymodaq/{daq_utils → utils}/parameter/ioxml.py +12 -6
  94. pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/__init__.py +1 -3
  95. pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/filedir.py +1 -1
  96. pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/itemselect.py +3 -0
  97. pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/led.py +1 -1
  98. pymodaq/utils/parameter/pymodaq_ptypes/pixmap.py +161 -0
  99. pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/slide.py +1 -1
  100. pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/table.py +1 -1
  101. pymodaq/utils/parameter/utils.py +206 -11
  102. pymodaq/utils/plotting/data_viewers/__init__.py +6 -0
  103. pymodaq/utils/plotting/data_viewers/viewer.py +393 -0
  104. pymodaq/utils/plotting/data_viewers/viewer0D.py +251 -0
  105. pymodaq/utils/plotting/data_viewers/viewer1D.py +574 -0
  106. pymodaq/{daq_utils → utils}/plotting/data_viewers/viewer1Dbasic.py +8 -3
  107. pymodaq/{daq_utils → utils}/plotting/data_viewers/viewer2D.py +292 -357
  108. pymodaq/{daq_utils → utils}/plotting/data_viewers/viewer2D_basic.py +58 -75
  109. pymodaq/utils/plotting/data_viewers/viewerND.py +738 -0
  110. pymodaq/{daq_utils → utils}/plotting/gant_chart.py +2 -2
  111. pymodaq/{daq_utils → utils}/plotting/items/axis_scaled.py +4 -2
  112. pymodaq/{daq_utils → utils}/plotting/items/image.py +8 -6
  113. pymodaq/utils/plotting/navigator.py +355 -0
  114. pymodaq/utils/plotting/scan_selector.py +480 -0
  115. pymodaq/utils/plotting/utils/axes_viewer.py +88 -0
  116. pymodaq/utils/plotting/utils/filter.py +538 -0
  117. pymodaq/utils/plotting/utils/lineout.py +224 -0
  118. pymodaq/{daq_utils → utils}/plotting/utils/plot_utils.py +196 -84
  119. pymodaq/{daq_utils → utils}/plotting/utils/signalND.py +21 -13
  120. pymodaq/utils/plotting/widgets.py +76 -0
  121. pymodaq/utils/scanner/__init__.py +10 -0
  122. pymodaq/utils/scanner/scan_factory.py +204 -0
  123. pymodaq/utils/scanner/scanner.py +271 -0
  124. pymodaq/utils/scanner/scanners/_1d_scanners.py +117 -0
  125. pymodaq/utils/scanner/scanners/_2d_scanners.py +293 -0
  126. pymodaq/utils/scanner/scanners/sequential.py +192 -0
  127. pymodaq/utils/scanner/scanners/tabular.py +294 -0
  128. pymodaq/utils/scanner/utils.py +83 -0
  129. pymodaq/utils/slicing.py +47 -0
  130. pymodaq/utils/svg/__init__.py +6 -0
  131. pymodaq/utils/svg/svg_renderer.py +20 -0
  132. pymodaq/utils/svg/svg_view.py +35 -0
  133. pymodaq/utils/svg/svg_viewer2D.py +51 -0
  134. pymodaq/{daq_utils → utils}/tcp_server_client.py +36 -37
  135. pymodaq/{daq_utils → utils}/tree_layout/tree_layout_main.py +50 -35
  136. pymodaq/utils/units.py +216 -0
  137. pymodaq-4.0.1.dist-info/METADATA +159 -0
  138. {pymodaq-3.6.12.dist-info → pymodaq-4.0.1.dist-info}/RECORD +167 -170
  139. {pymodaq-3.6.12.dist-info → pymodaq-4.0.1.dist-info}/WHEEL +1 -2
  140. pymodaq-4.0.1.dist-info/entry_points.txt +8 -0
  141. pymodaq/daq_move/daq_move_gui.py +0 -279
  142. pymodaq/daq_move/daq_move_gui.ui +0 -534
  143. pymodaq/daq_move/daq_move_main.py +0 -1042
  144. pymodaq/daq_move/process_from_QtDesigner_DAQ_Move_GUI.bat +0 -2
  145. pymodaq/daq_move/utility_classes.py +0 -671
  146. pymodaq/daq_scan.py +0 -2160
  147. pymodaq/daq_utils/array_manipulation.py +0 -386
  148. pymodaq/daq_utils/config.py +0 -273
  149. pymodaq/daq_utils/conftests.py +0 -7
  150. pymodaq/daq_utils/custom_parameter_tree.py +0 -9
  151. pymodaq/daq_utils/daq_enums.py +0 -133
  152. pymodaq/daq_utils/daq_utils.py +0 -1402
  153. pymodaq/daq_utils/exceptions.py +0 -71
  154. pymodaq/daq_utils/gui_utils/custom_app.py +0 -103
  155. pymodaq/daq_utils/gui_utils/file_io.py +0 -75
  156. pymodaq/daq_utils/gui_utils/widgets/spinbox.py +0 -9
  157. pymodaq/daq_utils/h5exporter_hyperspy.py +0 -115
  158. pymodaq/daq_utils/h5exporters.py +0 -242
  159. pymodaq/daq_utils/h5modules.py +0 -1559
  160. pymodaq/daq_utils/h5utils.py +0 -241
  161. pymodaq/daq_utils/managers/action_manager.py +0 -236
  162. pymodaq/daq_utils/managers/parameter_manager.py +0 -57
  163. pymodaq/daq_utils/math_utils.py +0 -705
  164. pymodaq/daq_utils/parameter/__init__.py +0 -1
  165. pymodaq/daq_utils/parameter/oldpymodaq_ptypes.py +0 -1626
  166. pymodaq/daq_utils/parameter/pymodaq_ptypes/pixmap.py +0 -85
  167. pymodaq/daq_utils/parameter/utils.py +0 -136
  168. pymodaq/daq_utils/plotting/data_viewers/__init__.py +0 -0
  169. pymodaq/daq_utils/plotting/data_viewers/process_from_QtDesigner_0DViewer_GUI.bat +0 -2
  170. pymodaq/daq_utils/plotting/data_viewers/viewer0D.py +0 -204
  171. pymodaq/daq_utils/plotting/data_viewers/viewer0D_GUI.py +0 -89
  172. pymodaq/daq_utils/plotting/data_viewers/viewer0D_GUI.ui +0 -131
  173. pymodaq/daq_utils/plotting/data_viewers/viewer1D.py +0 -781
  174. pymodaq/daq_utils/plotting/data_viewers/viewerND.py +0 -894
  175. pymodaq/daq_utils/plotting/data_viewers/viewerbase.py +0 -64
  176. pymodaq/daq_utils/plotting/items/__init__.py +0 -0
  177. pymodaq/daq_utils/plotting/navigator.py +0 -500
  178. pymodaq/daq_utils/plotting/scan_selector.py +0 -289
  179. pymodaq/daq_utils/plotting/utils/__init__.py +0 -0
  180. pymodaq/daq_utils/plotting/utils/filter.py +0 -236
  181. pymodaq/daq_utils/plotting/viewer0D/__init__.py +0 -0
  182. pymodaq/daq_utils/plotting/viewer0D/viewer0D_main.py +0 -4
  183. pymodaq/daq_utils/plotting/viewer1D/__init__.py +0 -0
  184. pymodaq/daq_utils/plotting/viewer1D/viewer1D_main.py +0 -4
  185. pymodaq/daq_utils/plotting/viewer1D/viewer1Dbasic.py +0 -4
  186. pymodaq/daq_utils/plotting/viewer2D/viewer_2D_basic.py +0 -4
  187. pymodaq/daq_utils/plotting/viewer2D/viewer_2D_main.py +0 -4
  188. pymodaq/daq_utils/plotting/viewerND/__init__.py +0 -0
  189. pymodaq/daq_utils/plotting/viewerND/viewerND_main.py +0 -4
  190. pymodaq/daq_utils/scanner.py +0 -1289
  191. pymodaq/daq_utils/tree_layout/__init__.py +0 -0
  192. pymodaq/daq_viewer/__init__.py +0 -0
  193. pymodaq/daq_viewer/daq_gui_settings.py +0 -237
  194. pymodaq/daq_viewer/daq_gui_settings.ui +0 -441
  195. pymodaq/daq_viewer/daq_viewer_main.py +0 -2225
  196. pymodaq/daq_viewer/process_from_QtDesigner_DAQ_GUI_settings.bat +0 -2
  197. pymodaq/daq_viewer/utility_classes.py +0 -673
  198. pymodaq/examples/logger_image/__init__.py +0 -0
  199. pymodaq/examples/logger_image/logger_displayer.py +0 -121
  200. pymodaq/examples/logger_image/setup.svg +0 -3119
  201. pymodaq/examples/logger_image/setup_svg.py +0 -114
  202. pymodaq/h5browser.py +0 -39
  203. pymodaq/utils/scanner.py +0 -15
  204. pymodaq-3.6.12.dist-info/METADATA +0 -39
  205. pymodaq-3.6.12.dist-info/entry_points.txt +0 -8
  206. pymodaq-3.6.12.dist-info/top_level.txt +0 -1
  207. /pymodaq/{daq_analysis → post_treatment/daq_analysis}/__init__.py +0 -0
  208. /pymodaq/{daq_measurement → post_treatment/daq_measurement}/__init__.py +0 -0
  209. /pymodaq/{daq_measurement → post_treatment/daq_measurement}/daq_measurement_GUI.py +0 -0
  210. /pymodaq/{daq_measurement → post_treatment/daq_measurement}/daq_measurement_GUI.ui +0 -0
  211. /pymodaq/{daq_measurement → post_treatment/daq_measurement}/process_from_QtDesigner_DAQ_Measurement_GUI.bat +0 -0
  212. /pymodaq/{daq_utils → utils}/Tuto innosetup/Tuto innosetup.odt +0 -0
  213. /pymodaq/{daq_utils → utils}/Tuto innosetup/Tuto innosetup.pdf +0 -0
  214. /pymodaq/{daq_move → utils/db}/__init__.py +0 -0
  215. /pymodaq/{daq_utils → utils/db/db_logger}/__init__.py +0 -0
  216. /pymodaq/{daq_utils → utils}/gui_utils/dock.py +0 -0
  217. /pymodaq/{daq_utils → utils}/gui_utils/list_picker.py +0 -0
  218. /pymodaq/{daq_utils/abstract → utils/managers}/__init__.py +0 -0
  219. /pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/bool.py +0 -0
  220. /pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/date.py +0 -0
  221. /pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/list.py +0 -0
  222. /pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/numeric.py +0 -0
  223. /pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/tableview.py +0 -0
  224. /pymodaq/{daq_utils → utils}/parameter/pymodaq_ptypes/text.py +0 -0
  225. /pymodaq/{daq_utils/db → utils/plotting}/__init__.py +0 -0
  226. /pymodaq/{daq_utils → utils}/plotting/image_viewer.py +0 -0
  227. /pymodaq/{daq_utils/db/db_logger → utils/plotting/items}/__init__.py +0 -0
  228. /pymodaq/{daq_utils → utils}/plotting/items/crosshair.py +0 -0
  229. /pymodaq/{daq_utils/managers → utils/plotting/utils}/__init__.py +0 -0
  230. /pymodaq/{daq_utils → utils}/qvariant.py +0 -0
  231. /pymodaq/{daq_utils/plotting/viewer2D → utils/scanner/scanners}/__init__.py +0 -0
  232. /pymodaq/{daq_utils/plotting → utils/tree_layout}/__init__.py +0 -0
  233. {pymodaq-3.6.12.dist-info → pymodaq-4.0.1.dist-info/licenses}/LICENSE +0 -0
@@ -0,0 +1,448 @@
1
+ from abc import abstractproperty
2
+
3
+ from os import environ
4
+ import sys
5
+ import datetime
6
+ from pathlib import Path
7
+ from typing import Union
8
+
9
+ import toml
10
+ from qtpy.QtCore import QObject
11
+ from pyqtgraph.parametertree import Parameter, ParameterTree
12
+ from qtpy import QtWidgets, QtCore
13
+
14
+ try:
15
+ USER = environ['USERNAME'] if sys.platform == 'win32' else environ['USER']
16
+ except:
17
+ USER = 'unknown_user'
18
+
19
+ CONFIG_BASE_PATH = Path(environ['PROGRAMDATA']) if sys.platform == 'win32' else \
20
+ Path('Library/Application Support') if sys.platform == 'darwin' else Path('/etc')
21
+
22
+
23
+ def replace_file_extension(filename: str, ext: str):
24
+ """Replace the extension of a file by the specified one, without the dot"""
25
+ file_name = Path(filename).stem # remove eventual extensions
26
+ if ext[0] == '.':
27
+ ext = ext[1:]
28
+ file_name += '.' + ext
29
+ return file_name
30
+
31
+
32
+ def getitem_recursive(dic, *args, ndepth=0):
33
+ """Will scan recursively a dictionary in order to get the item defined by the iterable args
34
+
35
+ Parameters
36
+ ----------
37
+ dic: dict
38
+ the dictionary to scan
39
+ args: an iterable of str
40
+ keys of the dict
41
+ ndepth: int
42
+ by default (0) get the last element defined by args. 1 would mean it get the parent dict, 2 the parent of the
43
+ parent...
44
+ Returns
45
+ -------
46
+ object or dict
47
+ """
48
+ args = list(args)
49
+ while len(args) > ndepth:
50
+ dic = dic[args.pop(0)]
51
+ return dic
52
+
53
+
54
+ def get_set_path(a_base_path: Path, dir_name: str) -> Path:
55
+ path_to_get = a_base_path.joinpath(dir_name)
56
+ if not path_to_get.is_dir():
57
+ try:
58
+ path_to_get.mkdir()
59
+ except PermissionError as e:
60
+ print(f"Cannot create local config folder at this location: {path_to_get}"
61
+ f", try using admin rights. "
62
+ f"Changing the not permitted path to a user one: {Path.home().joinpath(dir_name)}.")
63
+ path_to_get = Path.home().joinpath(dir_name)
64
+ if not path_to_get.is_dir():
65
+ path_to_get.mkdir()
66
+ return path_to_get
67
+
68
+
69
+ def get_set_local_dir(user=False) -> Path:
70
+ """Defines, creates and returns a local folder where configuration files will be saved
71
+
72
+ Depending on the os the configurations files will be stored in CONFIG_BASE_PATH, then
73
+ each user will have another one created that could override the default and system-wide base folder
74
+
75
+ Parameters
76
+ ----------
77
+ user: bool
78
+ if False get the system-wide folder, otherwise the user folder
79
+
80
+ Returns
81
+ -------
82
+ Path: the local path
83
+ """
84
+ if user:
85
+ local_path = get_set_path(Path.home(), '.pymodaq')
86
+ else:
87
+ local_path = get_set_path(CONFIG_BASE_PATH, '.pymodaq')
88
+ return local_path
89
+
90
+
91
+ def get_config_file(config_file_name: str, user=False):
92
+ return get_set_local_dir(user).joinpath(replace_file_extension(config_file_name, 'toml'))
93
+
94
+
95
+ def get_set_config_dir(config_name='config', user=False):
96
+ """Creates a folder in the local config directory to store specific configuration files
97
+
98
+ Parameters
99
+ ----------
100
+ config_name: (str) name of the configuration folder
101
+ user: bool
102
+ if False get the system-wide folder, otherwise the user folder
103
+
104
+ Returns
105
+ -------
106
+ Path
107
+
108
+ See Also
109
+ --------
110
+ get_set_local_dir
111
+ """
112
+ return get_set_path(get_set_local_dir(user=user), config_name)
113
+
114
+
115
+ def get_set_log_path():
116
+ """ creates and return the config folder path for log files
117
+ """
118
+ return get_set_config_dir('log')
119
+
120
+
121
+ def get_set_preset_path():
122
+ """ creates and return the config folder path for managers files
123
+ """
124
+ return get_set_config_dir('preset_configs')
125
+
126
+
127
+ def get_set_batch_path():
128
+ """ creates and return the config folder path for managers files
129
+ """
130
+ return get_set_config_dir('batch_configs')
131
+
132
+
133
+ def get_set_pid_path():
134
+ """ creates and return the config folder path for PID files
135
+ """
136
+ return get_set_config_dir('pid_configs')
137
+
138
+
139
+ def get_set_layout_path():
140
+ """ creates and return the config folder path for layout files
141
+ """
142
+ return get_set_config_dir('layout_configs')
143
+
144
+
145
+ def get_set_remote_path():
146
+ """ creates and return the config folder path for remote (shortcuts or joystick) files
147
+ """
148
+ return get_set_config_dir('remote_configs')
149
+
150
+
151
+ def get_set_overshoot_path():
152
+ """ creates and return the config folder path for overshoot files
153
+ """
154
+ return get_set_config_dir('overshoot_configs')
155
+
156
+
157
+ def get_set_roi_path():
158
+ """ creates and return the config folder path for managers files
159
+ """
160
+ return get_set_config_dir('roi_configs')
161
+
162
+
163
+ def create_toml_from_dict(mydict: dict, dest_path: Path):
164
+ """Create a Toml file at a given path from a dictionnary"""
165
+ dest_path.write_text(toml.dumps(mydict))
166
+
167
+
168
+ def check_config(config_base: dict, config_local: dict):
169
+ """Compare two configuration dictionaries. Adding missing keys
170
+
171
+ Parameters
172
+ ----------
173
+ config_base: dict
174
+ The base dictionaries with possible new keys
175
+ config_local: dict
176
+ a dict from a local config file potentially missing keys
177
+
178
+ Returns
179
+ -------
180
+ bool: True if keys where missing else False
181
+ """
182
+ status = False
183
+ for key in config_base:
184
+ if key in config_local:
185
+ if isinstance(config_base[key], dict):
186
+ status = status or check_config(config_base[key], config_local[key])
187
+ else:
188
+ config_local[key] = config_base[key]
189
+ status = True
190
+ return status
191
+
192
+
193
+ def copy_template_config(config_file_name: str = 'config', source_path: Union[Path, str] = None,
194
+ dest_path: Union[Path, str] = None):
195
+ """Get a toml file path and copy it
196
+
197
+ the destination is made of a given folder path (or the system-wide local path by default) and the config_file_name
198
+ appended by the suffix '.toml'
199
+
200
+ The source file (or pymodaq config template path by default) is read and dumped in this destination file
201
+
202
+ Parameters
203
+ ----------
204
+ config_file_name: str
205
+ the name of the destination config file
206
+ source_path: Path or str
207
+ the path of the toml source to be copied
208
+ dest_path: Path or str
209
+ the destination path of the copied config
210
+
211
+ Returns
212
+ -------
213
+ Path: the path of the copied file
214
+ """
215
+ if dest_path is None:
216
+ dest_path = get_set_local_dir()
217
+
218
+ file_name = Path(config_file_name).stem # remove eventual extensions
219
+ file_name += '.toml'
220
+ dest_path_with_filename = dest_path.joinpath(file_name)
221
+
222
+ if source_path is None:
223
+ config_template_dict = toml.load(Path(__file__).parent.parent.joinpath('resources/config_template.toml'))
224
+ else:
225
+ config_template_dict = toml.load(Path(source_path))
226
+
227
+ create_toml_from_dict(config_template_dict, dest_path_with_filename)
228
+ return dest_path_with_filename
229
+
230
+
231
+ def load_system_config_and_update_from_user(config_file_name: str):
232
+ """load from a system-wide config file, update it from the user config file
233
+
234
+ Parameters
235
+ ----------
236
+ config_file_name: str
237
+ The config file to be loaded
238
+ Returns
239
+ -------
240
+ dict: contains the toml system-wide file update with the user file
241
+ """
242
+ config_dict = dict([])
243
+ toml_base_path = get_config_file(config_file_name, user=False)
244
+ if toml_base_path.is_file():
245
+ config_dict = toml.load(toml_base_path)
246
+ toml_user_path = get_config_file(config_file_name, user=True)
247
+ if toml_user_path.is_file():
248
+ config_dict.update(toml.load(toml_user_path))
249
+ return config_dict
250
+
251
+
252
+ class ConfigError(Exception):
253
+ pass
254
+
255
+
256
+ class BaseConfig:
257
+ """Base class to manage configuration files
258
+
259
+ Should be subclassed with proper class attributes for each configuration file you need with pymodaq
260
+
261
+ Attributes
262
+ ----------
263
+ config_name: str
264
+ The name with which the configuration will be saved
265
+ config_template_path: Path
266
+ The Path of the template from which the config is constructed
267
+
268
+ """
269
+ config_template_path: Path = abstractproperty()
270
+ config_name: str = abstractproperty()
271
+
272
+ def __init__(self):
273
+ self._config = self.load_config(self.config_name, self.config_template_path)
274
+
275
+ def __call__(self, *args):
276
+ try:
277
+ ret = getitem_recursive(self._config, *args)
278
+ except KeyError as e:
279
+ raise ConfigError(f'the path {args} does not exist in your configuration toml file, check '
280
+ f'your pymodaq_local folder')
281
+ return ret
282
+
283
+ def to_dict(self):
284
+ return self._config
285
+
286
+ def __getitem__(self, item):
287
+ """for backcompatibility when it was a dictionnary"""
288
+ if isinstance(item, tuple):
289
+ return getitem_recursive(self._config, *item)
290
+ else:
291
+ return self._config[item]
292
+
293
+ def __setitem__(self, key, value):
294
+ if isinstance(key, tuple):
295
+ dic = getitem_recursive(self._config, *key, ndepth=1)
296
+ dic[key[-1]] = value
297
+ else:
298
+ self._config[key] = value
299
+
300
+ def load_config(self, config_file_name, template_path: Path):
301
+ """Load a configuration file from both system-wide and user file
302
+
303
+ check also if missing entries in the configuration file compared to the template"""
304
+ toml_base_path = get_config_file(config_file_name, user=False)
305
+ toml_user_path = get_config_file(config_file_name, user=True)
306
+ if toml_base_path.is_file():
307
+ config = toml.load(toml_base_path)
308
+ config_template = toml.load(template_path)
309
+ if check_config(config_template, config): # check if all fields from template are there
310
+ # (could have been modified by some commits)
311
+ create_toml_from_dict(toml.dumps(config), toml_base_path)
312
+
313
+ else:
314
+ copy_template_config(config_file_name, template_path, toml_base_path.parent)
315
+
316
+ if not toml_user_path.is_file():
317
+ # create the author from environment variable
318
+ config_dict = self.dict_to_add_to_user()
319
+ if config_dict is not None:
320
+ create_toml_from_dict(config_dict, toml_user_path)
321
+
322
+ config_dict = load_system_config_and_update_from_user(config_file_name)
323
+ return config_dict
324
+
325
+ def dict_to_add_to_user(self):
326
+ """To subclass"""
327
+ return None
328
+
329
+ @property
330
+ def config_path(self):
331
+ """Get the user config path"""
332
+ return get_config_file(self.config_name, user=True)
333
+
334
+ @property
335
+ def system_config_path(self):
336
+ """Get the system_wide config path"""
337
+ return get_config_file(self.config_name, user=False)
338
+
339
+ def save(self):
340
+ """Save the current Config object into the user toml file"""
341
+ self.config_path.write_text(toml.dumps(self.to_dict()))
342
+
343
+
344
+ class Config(BaseConfig):
345
+ """Main class to deal with configuration values for PyMoDAQ"""
346
+ config_template_path = Path(__file__).parent.parent.joinpath('resources/config_template.toml')
347
+ config_name = 'config_pymodaq'
348
+
349
+ def dict_to_add_to_user(self):
350
+ """To subclass"""
351
+ return dict(user=dict(name=USER))
352
+
353
+
354
+ class TreeFromToml(QObject):
355
+ def __init__(self, config: Config = None):
356
+ super().__init__()
357
+ if config is None:
358
+ config = Config()
359
+ self._config = config
360
+ params = [{'title': 'Config path', 'name': 'config_path', 'type': 'str', 'value': str(self._config.config_path),
361
+ 'readonly': True}]
362
+ params.extend(self.dict_to_param(config.to_dict()))
363
+
364
+ self.settings = Parameter.create(title='settings', name='settings', type='group', children=params)
365
+ self.settings_tree = ParameterTree()
366
+ self.settings_tree.setParameters(self.settings, showTop=False)
367
+
368
+ def show_dialog(self):
369
+
370
+ self.dialog = QtWidgets.QDialog()
371
+ self.dialog.setWindowTitle('Please enter new configuration values!')
372
+ self.dialog.setLayout(QtWidgets.QVBoxLayout())
373
+ buttonBox = QtWidgets.QDialogButtonBox(parent=self.dialog)
374
+
375
+ buttonBox.addButton('Save', buttonBox.AcceptRole)
376
+ buttonBox.accepted.connect(self.dialog.accept)
377
+ buttonBox.addButton('Cancel', buttonBox.RejectRole)
378
+ buttonBox.rejected.connect(self.dialog.reject)
379
+
380
+ self.dialog.layout().addWidget(self.settings_tree)
381
+ self.dialog.layout().addWidget(buttonBox)
382
+ self.dialog.setWindowTitle('Configuration entries')
383
+ res = self.dialog.exec()
384
+
385
+ if res == self.dialog.Accepted:
386
+ with open(self._config.config_path, 'w') as f:
387
+ config_dict = self.param_to_dict(self.settings)
388
+ config_dict.pop('config_path')
389
+ create_toml_from_dict(config_dict, self._config.config_path)
390
+
391
+ @classmethod
392
+ def param_to_dict(cls, param: Parameter) -> dict:
393
+ config = dict()
394
+ for child in param.children():
395
+ if 'group' in child.opts['type']:
396
+ config[child.name()] = cls.param_to_dict(child)
397
+ else:
398
+ if child.opts['type'] == 'datetime':
399
+ config[child.name()] = datetime.fromtimestamp(
400
+ child.value().toSecsSinceEpoch()) # convert QDateTime to python datetime
401
+ elif child.opts['type'] == 'date':
402
+ qdt = QtCore.QDateTime()
403
+ qdt.setDate(child.value())
404
+ pdt = datetime.fromtimestamp(qdt.toSecsSinceEpoch())
405
+ config[child.name()] = pdt.date()
406
+ elif child.opts['type'] == 'list':
407
+ config[child.name()] = child.opts['limits']
408
+ else:
409
+ config[child.name()] = child.value()
410
+ return config
411
+
412
+ @classmethod
413
+ def dict_to_param(cls, config: dict) -> Parameter:
414
+ params = []
415
+ for key in config:
416
+ if isinstance(config[key], dict):
417
+ params.append({'title': f'{key.capitalize()}:', 'name': key, 'type': 'group',
418
+ 'children': cls.dict_to_param(config[key]),
419
+ 'expanded': 'user' in key.lower() or 'general' in key.lower()})
420
+ else:
421
+ param = {'title': f'{key.capitalize()}:', 'name': key, 'value': config[key]}
422
+ if isinstance(config[key], float):
423
+ param['type'] = 'float'
424
+ elif isinstance(config[key], bool): # placed before int because a bool is an instance of int
425
+ param['type'] = 'bool'
426
+ elif isinstance(config[key], int):
427
+ param['type'] = 'int'
428
+ elif isinstance(config[key], datetime.datetime):
429
+ param['type'] = 'datetime'
430
+ elif isinstance(config[key], datetime.date):
431
+ param['type'] = 'date'
432
+ elif isinstance(config[key], str):
433
+ param['type'] = 'str'
434
+ elif isinstance(config[key], list):
435
+ param['type'] = 'list'
436
+ param['limits'] = config[key]
437
+ param['value'] = config[key][0]
438
+ param['show_pb'] = True
439
+ params.append(param)
440
+ return params
441
+
442
+
443
+ if __name__ == '__main__':
444
+
445
+ config = Config()
446
+ config('style', 'darkstyle')
447
+ assert config('style', 'darkstyle') == config['style']['darkstyle']
448
+
@@ -0,0 +1,5 @@
1
+ import pytest
2
+
3
+ qtbotskip = True
4
+
5
+ main_modules_skip = True