cgse 2023.38.0__py3-none-any.whl → 2024.1.4__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 (653) hide show
  1. README.md +27 -0
  2. bump.py +85 -0
  3. cgse-2024.1.4.dist-info/METADATA +38 -0
  4. cgse-2024.1.4.dist-info/RECORD +5 -0
  5. {cgse-2023.38.0.dist-info → cgse-2024.1.4.dist-info}/WHEEL +1 -2
  6. cgse-2023.38.0.dist-info/COPYING +0 -674
  7. cgse-2023.38.0.dist-info/COPYING.LESSER +0 -165
  8. cgse-2023.38.0.dist-info/METADATA +0 -144
  9. cgse-2023.38.0.dist-info/RECORD +0 -649
  10. cgse-2023.38.0.dist-info/entry_points.txt +0 -75
  11. cgse-2023.38.0.dist-info/top_level.txt +0 -2
  12. egse/__init__.py +0 -12
  13. egse/__main__.py +0 -32
  14. egse/aeu/aeu.py +0 -5235
  15. egse/aeu/aeu_awg.yaml +0 -265
  16. egse/aeu/aeu_crio.yaml +0 -273
  17. egse/aeu/aeu_cs.py +0 -626
  18. egse/aeu/aeu_devif.py +0 -321
  19. egse/aeu/aeu_main_ui.py +0 -912
  20. egse/aeu/aeu_metrics.py +0 -131
  21. egse/aeu/aeu_protocol.py +0 -463
  22. egse/aeu/aeu_psu.yaml +0 -204
  23. egse/aeu/aeu_ui.py +0 -873
  24. egse/aeu/arbdata/FccdRead.arb +0 -2
  25. egse/aeu/arbdata/FccdRead_min_points.arb +0 -2
  26. egse/aeu/arbdata/HeaterSync_FccdRead.arb +0 -2
  27. egse/aeu/arbdata/HeaterSync_ccdRead25.arb +0 -2
  28. egse/aeu/arbdata/HeaterSync_ccdRead31_25.arb +0 -2
  29. egse/aeu/arbdata/HeaterSync_ccdRead37_50.arb +0 -2
  30. egse/aeu/arbdata/HeaterSync_ccdRead43_75.arb +0 -2
  31. egse/aeu/arbdata/HeaterSync_ccdRead50.arb +0 -2
  32. egse/aeu/arbdata/Heater_FccdRead_min_points.arb +0 -2
  33. egse/aeu/arbdata/ccdRead25.arb +0 -2
  34. egse/aeu/arbdata/ccdRead25_150ms.arb +0 -2
  35. egse/aeu/arbdata/ccdRead31_25.arb +0 -2
  36. egse/aeu/arbdata/ccdRead31_25_150ms.arb +0 -2
  37. egse/aeu/arbdata/ccdRead37_50.arb +0 -2
  38. egse/aeu/arbdata/ccdRead37_50_150ms.arb +0 -2
  39. egse/aeu/arbdata/ccdRead43_75.arb +0 -2
  40. egse/aeu/arbdata/ccdRead43_75_150ms.arb +0 -2
  41. egse/aeu/arbdata/ccdRead50.arb +0 -2
  42. egse/aeu/arbdata/ccdRead50_150ms.arb +0 -2
  43. egse/alert/__init__.py +0 -1049
  44. egse/alert/alertman.yaml +0 -37
  45. egse/alert/alertman_cs.py +0 -234
  46. egse/alert/alertman_ui.py +0 -603
  47. egse/alert/gsm/beaglebone.py +0 -138
  48. egse/alert/gsm/beaglebone.yaml +0 -51
  49. egse/alert/gsm/beaglebone_cs.py +0 -108
  50. egse/alert/gsm/beaglebone_devif.py +0 -130
  51. egse/alert/gsm/beaglebone_protocol.py +0 -48
  52. egse/bits.py +0 -318
  53. egse/camera.py +0 -44
  54. egse/collimator/__init__.py +0 -0
  55. egse/collimator/fcul/__init__.py +0 -0
  56. egse/collimator/fcul/ogse.py +0 -1077
  57. egse/collimator/fcul/ogse.yaml +0 -14
  58. egse/collimator/fcul/ogse_cs.py +0 -154
  59. egse/collimator/fcul/ogse_devif.py +0 -358
  60. egse/collimator/fcul/ogse_protocol.py +0 -129
  61. egse/collimator/fcul/ogse_sim.py +0 -431
  62. egse/collimator/fcul/ogse_ui.py +0 -1108
  63. egse/command.py +0 -699
  64. egse/config.py +0 -410
  65. egse/confman/__init__.py +0 -1015
  66. egse/confman/confman.yaml +0 -67
  67. egse/confman/confman_cs.py +0 -239
  68. egse/confman/confman_ui.py +0 -381
  69. egse/confman/setup_ui.py +0 -565
  70. egse/control.py +0 -442
  71. egse/coordinates/__init__.py +0 -531
  72. egse/coordinates/avoidance.py +0 -103
  73. egse/coordinates/cslmodel.py +0 -127
  74. egse/coordinates/laser_tracker_to_dict.py +0 -120
  75. egse/coordinates/point.py +0 -707
  76. egse/coordinates/pyplot.py +0 -195
  77. egse/coordinates/referenceFrame.py +0 -1279
  78. egse/coordinates/refmodel.py +0 -737
  79. egse/coordinates/rotationMatrix.py +0 -85
  80. egse/coordinates/transform3d_addon.py +0 -419
  81. egse/csl/__init__.py +0 -50
  82. egse/csl/commanding.py +0 -78
  83. egse/csl/icons/hexapod-connected-selected.svg +0 -30
  84. egse/csl/icons/hexapod-connected.svg +0 -30
  85. egse/csl/icons/hexapod-homing-selected.svg +0 -68
  86. egse/csl/icons/hexapod-homing.svg +0 -68
  87. egse/csl/icons/hexapod-retract-selected.svg +0 -56
  88. egse/csl/icons/hexapod-retract.svg +0 -51
  89. egse/csl/icons/hexapod-zero-selected.svg +0 -56
  90. egse/csl/icons/hexapod-zero.svg +0 -56
  91. egse/csl/icons/logo-puna.svg +0 -92
  92. egse/csl/icons/stop.svg +0 -1
  93. egse/csl/initialisation.py +0 -102
  94. egse/csl/mech_pos_settings.yaml +0 -18
  95. egse/das.py +0 -1247
  96. egse/das.yaml +0 -7
  97. egse/data/conf/SETUP_CSL_00000_170620_150000.yaml +0 -5
  98. egse/data/conf/SETUP_CSL_00001_170620_151010.yaml +0 -69
  99. egse/data/conf/SETUP_CSL_00002_170620_151020.yaml +0 -69
  100. egse/data/conf/SETUP_CSL_00003_170620_151030.yaml +0 -69
  101. egse/data/conf/SETUP_CSL_00004_170620_151040.yaml +0 -69
  102. egse/data/conf/SETUP_CSL_00005_170620_151050.yaml +0 -69
  103. egse/data/conf/SETUP_CSL_00006_170620_151060.yaml +0 -69
  104. egse/data/conf/SETUP_CSL_00007_170620_151070.yaml +0 -69
  105. egse/data/conf/SETUP_CSL_00008_170620_151080.yaml +0 -75
  106. egse/data/conf/SETUP_CSL_00010_210308_083016.yaml +0 -138
  107. egse/data/conf/SETUP_INTA_00000_170620_150000.yaml +0 -4
  108. egse/data/conf/SETUP_SRON_00000_170620_150000.yaml +0 -4
  109. egse/decorators.py +0 -415
  110. egse/device.py +0 -269
  111. egse/dpu/__init__.py +0 -2681
  112. egse/dpu/ccd_ui.py +0 -508
  113. egse/dpu/dpu.py +0 -786
  114. egse/dpu/dpu.yaml +0 -153
  115. egse/dpu/dpu_cs.py +0 -272
  116. egse/dpu/dpu_ui.py +0 -668
  117. egse/dpu/fitsgen.py +0 -2077
  118. egse/dpu/fitsgen_test.py +0 -752
  119. egse/dpu/fitsgen_ui.py +0 -399
  120. egse/dpu/hdf5_model.py +0 -332
  121. egse/dpu/hdf5_ui.py +0 -277
  122. egse/dpu/hdf5_viewer.py +0 -506
  123. egse/dpu/hk_ui.py +0 -468
  124. egse/dpu_commands.py +0 -81
  125. egse/dsi/constants.py +0 -220
  126. egse/dsi/esl.py +0 -870
  127. egse/dsi/rmap.py +0 -1042
  128. egse/dsi/rmapci.py +0 -37
  129. egse/dsi/spw.py +0 -154
  130. egse/dsi/spw_state.py +0 -29
  131. egse/dummy.py +0 -258
  132. egse/dyndummy.py +0 -179
  133. egse/env.py +0 -278
  134. egse/exceptions.py +0 -88
  135. egse/fdir/__init__.py +0 -28
  136. egse/fdir/fdir_manager.py +0 -85
  137. egse/fdir/fdir_manager.yaml +0 -51
  138. egse/fdir/fdir_manager_controller.py +0 -228
  139. egse/fdir/fdir_manager_cs.py +0 -164
  140. egse/fdir/fdir_manager_interface.py +0 -25
  141. egse/fdir/fdir_remote.py +0 -73
  142. egse/fdir/fdir_remote.yaml +0 -37
  143. egse/fdir/fdir_remote_controller.py +0 -50
  144. egse/fdir/fdir_remote_cs.py +0 -97
  145. egse/fdir/fdir_remote_interface.py +0 -14
  146. egse/fdir/fdir_remote_popup.py +0 -31
  147. egse/fee/__init__.py +0 -114
  148. egse/fee/f_fee_register.yaml +0 -43
  149. egse/fee/fee.py +0 -631
  150. egse/fee/feesim.py +0 -750
  151. egse/fee/n_fee_hk.py +0 -761
  152. egse/fee/nfee.py +0 -187
  153. egse/filterwheel/__init__.py +0 -4
  154. egse/filterwheel/eksma/__init__.py +0 -24
  155. egse/filterwheel/eksma/fw8smc4.py +0 -661
  156. egse/filterwheel/eksma/fw8smc4.yaml +0 -121
  157. egse/filterwheel/eksma/fw8smc4_cs.py +0 -144
  158. egse/filterwheel/eksma/fw8smc4_devif.py +0 -473
  159. egse/filterwheel/eksma/fw8smc4_protocol.py +0 -81
  160. egse/filterwheel/eksma/fw8smc4_ui.py +0 -940
  161. egse/filterwheel/eksma/fw8smc5.py +0 -111
  162. egse/filterwheel/eksma/fw8smc5.yaml +0 -105
  163. egse/filterwheel/eksma/fw8smc5_controller.py +0 -307
  164. egse/filterwheel/eksma/fw8smc5_cs.py +0 -141
  165. egse/filterwheel/eksma/fw8smc5_interface.py +0 -65
  166. egse/filterwheel/eksma/fw8smc5_simulator.py +0 -29
  167. egse/filterwheel/eksma/fw8smc5_ui.py +0 -1068
  168. egse/filterwheel/eksma/testpythonfw.py +0 -215
  169. egse/fov/__init__.py +0 -65
  170. egse/fov/fov_hk.py +0 -712
  171. egse/fov/fov_ui.py +0 -861
  172. egse/fov/fov_ui_controller.py +0 -140
  173. egse/fov/fov_ui_model.py +0 -200
  174. egse/fov/fov_ui_view.py +0 -345
  175. egse/gimbal/__init__.py +0 -32
  176. egse/gimbal/symetrie/__init__.py +0 -26
  177. egse/gimbal/symetrie/alpha.py +0 -586
  178. egse/gimbal/symetrie/generic_gimbal_ui.py +0 -1521
  179. egse/gimbal/symetrie/gimbal.py +0 -877
  180. egse/gimbal/symetrie/gimbal.yaml +0 -168
  181. egse/gimbal/symetrie/gimbal_cs.py +0 -183
  182. egse/gimbal/symetrie/gimbal_protocol.py +0 -135
  183. egse/gimbal/symetrie/gimbal_ui.py +0 -361
  184. egse/gimbal/symetrie/pmac.py +0 -1006
  185. egse/gimbal/symetrie/pmac_regex.py +0 -83
  186. egse/graph.py +0 -132
  187. egse/gui/__init__.py +0 -47
  188. egse/gui/buttons.py +0 -378
  189. egse/gui/focalplane.py +0 -1281
  190. egse/gui/formatter.py +0 -10
  191. egse/gui/led.py +0 -162
  192. egse/gui/limitswitch.py +0 -143
  193. egse/gui/mechanisms.py +0 -588
  194. egse/gui/states.py +0 -148
  195. egse/gui/stripchart.py +0 -729
  196. egse/gui/switch.py +0 -112
  197. egse/h5.py +0 -274
  198. egse/help/__init__.py +0 -0
  199. egse/help/help_ui.py +0 -126
  200. egse/hexapod/__init__.py +0 -32
  201. egse/hexapod/symetrie/__init__.py +0 -138
  202. egse/hexapod/symetrie/alpha.py +0 -874
  203. egse/hexapod/symetrie/dynalpha.py +0 -1387
  204. egse/hexapod/symetrie/hexapod_ui.py +0 -1516
  205. egse/hexapod/symetrie/pmac.py +0 -1010
  206. egse/hexapod/symetrie/pmac_regex.py +0 -83
  207. egse/hexapod/symetrie/puna.py +0 -1167
  208. egse/hexapod/symetrie/puna.yaml +0 -193
  209. egse/hexapod/symetrie/puna_cs.py +0 -196
  210. egse/hexapod/symetrie/puna_protocol.py +0 -131
  211. egse/hexapod/symetrie/puna_ui.py +0 -434
  212. egse/hexapod/symetrie/punaplus.py +0 -107
  213. egse/hexapod/symetrie/zonda.py +0 -872
  214. egse/hexapod/symetrie/zonda.yaml +0 -337
  215. egse/hexapod/symetrie/zonda_cs.py +0 -172
  216. egse/hexapod/symetrie/zonda_devif.py +0 -415
  217. egse/hexapod/symetrie/zonda_protocol.py +0 -119
  218. egse/hexapod/symetrie/zonda_ui.py +0 -449
  219. egse/hk.py +0 -765
  220. egse/icons/aeu-cs-start.svg +0 -117
  221. egse/icons/aeu-cs-stop.svg +0 -118
  222. egse/icons/aeu-cs.svg +0 -107
  223. egse/icons/aeu_cs-started.svg +0 -112
  224. egse/icons/aeu_cs-stopped.svg +0 -112
  225. egse/icons/aeu_cs.svg +0 -55
  226. egse/icons/alert.svg +0 -1
  227. egse/icons/arrow-double-left.png +0 -0
  228. egse/icons/arrow-double-right.png +0 -0
  229. egse/icons/arrow-up.svg +0 -11
  230. egse/icons/backward.svg +0 -1
  231. egse/icons/busy.svg +0 -1
  232. egse/icons/cleaning.svg +0 -115
  233. egse/icons/color-scheme.svg +0 -1
  234. egse/icons/cs-connected-alert.svg +0 -91
  235. egse/icons/cs-connected-disabled.svg +0 -43
  236. egse/icons/cs-connected.svg +0 -89
  237. egse/icons/cs-not-connected.svg +0 -44
  238. egse/icons/double-left-arrow.svg +0 -1
  239. egse/icons/double-right-arrow.svg +0 -1
  240. egse/icons/erase-disabled.svg +0 -19
  241. egse/icons/erase.svg +0 -59
  242. egse/icons/fitsgen-start.svg +0 -47
  243. egse/icons/fitsgen-stop.svg +0 -48
  244. egse/icons/fitsgen.svg +0 -1
  245. egse/icons/forward.svg +0 -1
  246. egse/icons/fov-hk-start.svg +0 -33
  247. egse/icons/fov-hk-stop.svg +0 -37
  248. egse/icons/fov-hk.svg +0 -1
  249. egse/icons/front-desk.svg +0 -1
  250. egse/icons/home-actioned.svg +0 -15
  251. egse/icons/home-disabled.svg +0 -15
  252. egse/icons/home.svg +0 -13
  253. egse/icons/info.svg +0 -1
  254. egse/icons/invalid.png +0 -0
  255. egse/icons/led-green.svg +0 -20
  256. egse/icons/led-grey.svg +0 -20
  257. egse/icons/led-orange.svg +0 -20
  258. egse/icons/led-red.svg +0 -20
  259. egse/icons/led-square-green.svg +0 -134
  260. egse/icons/led-square-grey.svg +0 -134
  261. egse/icons/led-square-orange.svg +0 -134
  262. egse/icons/led-square-red.svg +0 -134
  263. egse/icons/limit-switch-all-green.svg +0 -115
  264. egse/icons/limit-switch-all-red.svg +0 -117
  265. egse/icons/limit-switch-el+.svg +0 -116
  266. egse/icons/limit-switch-el-.svg +0 -117
  267. egse/icons/location-marker.svg +0 -1
  268. egse/icons/logo-dpu.svg +0 -48
  269. egse/icons/logo-gimbal.svg +0 -112
  270. egse/icons/logo-huber.svg +0 -23
  271. egse/icons/logo-ogse.svg +0 -31
  272. egse/icons/logo-puna.svg +0 -92
  273. egse/icons/logo-tcs.svg +0 -29
  274. egse/icons/logo-zonda.svg +0 -66
  275. egse/icons/maximize.svg +0 -1
  276. egse/icons/meter.svg +0 -1
  277. egse/icons/more.svg +0 -45
  278. egse/icons/n-fee-hk-start.svg +0 -24
  279. egse/icons/n-fee-hk-stop.svg +0 -25
  280. egse/icons/n-fee-hk.svg +0 -83
  281. egse/icons/observing-off.svg +0 -46
  282. egse/icons/observing-on.svg +0 -46
  283. egse/icons/open-document-hdf5.png +0 -0
  284. egse/icons/open-document-hdf5.svg +0 -21
  285. egse/icons/ops-mode.svg +0 -1
  286. egse/icons/play-green.svg +0 -17
  287. egse/icons/plugged-disabled.svg +0 -27
  288. egse/icons/plugged.svg +0 -21
  289. egse/icons/pm_ui.svg +0 -1
  290. egse/icons/power-button-green.svg +0 -27
  291. egse/icons/power-button-red.svg +0 -27
  292. egse/icons/power-button.svg +0 -27
  293. egse/icons/radar.svg +0 -1
  294. egse/icons/radioactive.svg +0 -2
  295. egse/icons/reload.svg +0 -1
  296. egse/icons/remote-control-off.svg +0 -28
  297. egse/icons/remote-control-on.svg +0 -28
  298. egse/icons/repeat-blue.svg +0 -15
  299. egse/icons/repeat.svg +0 -1
  300. egse/icons/settings.svg +0 -1
  301. egse/icons/shrink.svg +0 -1
  302. egse/icons/shutter.svg +0 -1
  303. egse/icons/sign-off.svg +0 -1
  304. egse/icons/sign-on.svg +0 -1
  305. egse/icons/sim-mode.svg +0 -1
  306. egse/icons/small-buttons-go.svg +0 -20
  307. egse/icons/small-buttons-minus.svg +0 -51
  308. egse/icons/small-buttons-plus.svg +0 -51
  309. egse/icons/sponge.svg +0 -220
  310. egse/icons/start-button-disabled.svg +0 -84
  311. egse/icons/start-button.svg +0 -50
  312. egse/icons/stop-button-disabled.svg +0 -84
  313. egse/icons/stop-button.svg +0 -50
  314. egse/icons/stop-red.svg +0 -17
  315. egse/icons/stop.svg +0 -1
  316. egse/icons/switch-disabled-square.svg +0 -87
  317. egse/icons/switch-disabled.svg +0 -15
  318. egse/icons/switch-off-square.svg +0 -87
  319. egse/icons/switch-off.svg +0 -72
  320. egse/icons/switch-on-square.svg +0 -87
  321. egse/icons/switch-on.svg +0 -61
  322. egse/icons/temperature-control.svg +0 -44
  323. egse/icons/th_ui_logo.svg +0 -1
  324. egse/icons/unplugged.svg +0 -23
  325. egse/icons/unvalid.png +0 -0
  326. egse/icons/user-interface.svg +0 -1
  327. egse/icons/vacuum.svg +0 -1
  328. egse/icons/valid.png +0 -0
  329. egse/icons/zoom-to-pixel-dark.svg +0 -64
  330. egse/icons/zoom-to-pixel-white.svg +0 -36
  331. egse/images/big-rotation-stage.png +0 -0
  332. egse/images/connected-100.png +0 -0
  333. egse/images/cross.svg +0 -6
  334. egse/images/disconnected-100.png +0 -0
  335. egse/images/gui-icon.png +0 -0
  336. egse/images/home.svg +0 -6
  337. egse/images/info-icon.png +0 -0
  338. egse/images/led-black.svg +0 -89
  339. egse/images/led-green.svg +0 -85
  340. egse/images/led-orange.svg +0 -85
  341. egse/images/led-red.svg +0 -85
  342. egse/images/load-icon.png +0 -0
  343. egse/images/load-setup.png +0 -0
  344. egse/images/load.png +0 -0
  345. egse/images/pause.png +0 -0
  346. egse/images/play-button.svg +0 -8
  347. egse/images/play.png +0 -0
  348. egse/images/process-status.png +0 -0
  349. egse/images/restart.png +0 -0
  350. egse/images/search.png +0 -0
  351. egse/images/sma.png +0 -0
  352. egse/images/start.png +0 -0
  353. egse/images/stop-button.svg +0 -8
  354. egse/images/stop.png +0 -0
  355. egse/images/switch-off.svg +0 -48
  356. egse/images/switch-on.svg +0 -48
  357. egse/images/undo.png +0 -0
  358. egse/images/update-button.svg +0 -11
  359. egse/imageviewer/exposureselection.py +0 -475
  360. egse/imageviewer/imageviewer.py +0 -198
  361. egse/imageviewer/matchfocalplane.py +0 -179
  362. egse/imageviewer/subfieldposition.py +0 -133
  363. egse/lampcontrol/__init__.py +0 -4
  364. egse/lampcontrol/beaglebone/beaglebone.py +0 -178
  365. egse/lampcontrol/beaglebone/beaglebone.yaml +0 -62
  366. egse/lampcontrol/beaglebone/beaglebone_cs.py +0 -106
  367. egse/lampcontrol/beaglebone/beaglebone_devif.py +0 -150
  368. egse/lampcontrol/beaglebone/beaglebone_protocol.py +0 -73
  369. egse/lampcontrol/energetiq/__init__.py +0 -22
  370. egse/lampcontrol/energetiq/eq99.yaml +0 -98
  371. egse/lampcontrol/energetiq/lampEQ99.py +0 -283
  372. egse/lampcontrol/energetiq/lampEQ99_cs.py +0 -128
  373. egse/lampcontrol/energetiq/lampEQ99_devif.py +0 -158
  374. egse/lampcontrol/energetiq/lampEQ99_encode_decode_errors.py +0 -73
  375. egse/lampcontrol/energetiq/lampEQ99_protocol.py +0 -69
  376. egse/lampcontrol/energetiq/lampEQ99_ui.py +0 -465
  377. egse/lib/CentOS-7/EtherSpaceLink_v34_86.dylib +0 -0
  378. egse/lib/CentOS-8/ESL-RMAP_v34_86.dylib +0 -0
  379. egse/lib/CentOS-8/EtherSpaceLink_v34_86.dylib +0 -0
  380. egse/lib/Debian/ESL-RMAP_v34_86.dylib +0 -0
  381. egse/lib/Debian/EtherSpaceLink_v34_86.dylib +0 -0
  382. egse/lib/Debian/libetherspacelink_v35_21.dylib +0 -0
  383. egse/lib/Linux/ESL-RMAP_v34_86.dylib +0 -0
  384. egse/lib/Linux/EtherSpaceLink_v34_86.dylib +0 -0
  385. egse/lib/Ubuntu-20/ESL-RMAP_v34_86.dylib +0 -0
  386. egse/lib/Ubuntu-20/EtherSpaceLink_v34_86.dylib +0 -0
  387. egse/lib/gssw/python3-gssw_2.2.3+31f63c9f-1_all.deb +0 -0
  388. egse/lib/macOS/ESL-RMAP_v34_86.dylib +0 -0
  389. egse/lib/macOS/EtherSpaceLink_v34_86.dylib +0 -0
  390. egse/lib/ximc/__pycache__/pyximc.cpython-38 2.pyc +0 -0
  391. egse/lib/ximc/__pycache__/pyximc.cpython-38.pyc +0 -0
  392. egse/lib/ximc/libximc.framework/Frameworks/libbindy.dylib +0 -0
  393. egse/lib/ximc/libximc.framework/Frameworks/libxiwrapper.dylib +0 -0
  394. egse/lib/ximc/libximc.framework/Headers/ximc.h +0 -5510
  395. egse/lib/ximc/libximc.framework/Resources/Info.plist +0 -42
  396. egse/lib/ximc/libximc.framework/Resources/keyfile.sqlite +0 -0
  397. egse/lib/ximc/libximc.framework/libbindy.so +0 -0
  398. egse/lib/ximc/libximc.framework/libximc +0 -0
  399. egse/lib/ximc/libximc.framework/libximc.so +0 -0
  400. egse/lib/ximc/libximc.framework/libximc.so.7.0.0 +0 -0
  401. egse/lib/ximc/libximc.framework/libxiwrapper.so +0 -0
  402. egse/lib/ximc/pyximc.py +0 -922
  403. egse/listener.py +0 -73
  404. egse/logger/__init__.py +0 -243
  405. egse/logger/log_cs.py +0 -321
  406. egse/metrics.py +0 -98
  407. egse/mixin.py +0 -464
  408. egse/monitoring.py +0 -95
  409. egse/ni/alarms/__init__.py +0 -26
  410. egse/ni/alarms/cdaq9375.py +0 -300
  411. egse/ni/alarms/cdaq9375.yaml +0 -89
  412. egse/ni/alarms/cdaq9375_cs.py +0 -130
  413. egse/ni/alarms/cdaq9375_devif.py +0 -183
  414. egse/ni/alarms/cdaq9375_protocol.py +0 -48
  415. egse/obs_inspection.py +0 -163
  416. egse/observer.py +0 -41
  417. egse/obsid.py +0 -163
  418. egse/powermeter/__init__.py +0 -0
  419. egse/powermeter/ni/__init__.py +0 -38
  420. egse/powermeter/ni/cdaq9184.py +0 -224
  421. egse/powermeter/ni/cdaq9184.yaml +0 -73
  422. egse/powermeter/ni/cdaq9184_cs.py +0 -130
  423. egse/powermeter/ni/cdaq9184_devif.py +0 -201
  424. egse/powermeter/ni/cdaq9184_protocol.py +0 -48
  425. egse/powermeter/ni/cdaq9184_ui.py +0 -544
  426. egse/powermeter/thorlabs/__init__.py +0 -25
  427. egse/powermeter/thorlabs/pm100a.py +0 -380
  428. egse/powermeter/thorlabs/pm100a.yaml +0 -132
  429. egse/powermeter/thorlabs/pm100a_cs.py +0 -136
  430. egse/powermeter/thorlabs/pm100a_devif.py +0 -127
  431. egse/powermeter/thorlabs/pm100a_protocol.py +0 -80
  432. egse/powermeter/thorlabs/pm100a_ui.py +0 -725
  433. egse/process.py +0 -451
  434. egse/procman/__init__.py +0 -811
  435. egse/procman/cannot_start_process_popup.py +0 -43
  436. egse/procman/procman.yaml +0 -49
  437. egse/procman/procman_cs.py +0 -201
  438. egse/procman/procman_ui.py +0 -2081
  439. egse/protocol.py +0 -603
  440. egse/proxy.py +0 -522
  441. egse/randomwalk.py +0 -140
  442. egse/reg.py +0 -585
  443. egse/reload.py +0 -122
  444. egse/reprocess.py +0 -675
  445. egse/resource.py +0 -333
  446. egse/rst.py +0 -135
  447. egse/search.py +0 -182
  448. egse/serialdevice.py +0 -190
  449. egse/services.py +0 -212
  450. egse/services.yaml +0 -51
  451. egse/settings.py +0 -379
  452. egse/settings.yaml +0 -980
  453. egse/setup.py +0 -1180
  454. egse/shutter/__init__.py +0 -0
  455. egse/shutter/thorlabs/__init__.py +0 -19
  456. egse/shutter/thorlabs/ksc101.py +0 -205
  457. egse/shutter/thorlabs/ksc101.yaml +0 -105
  458. egse/shutter/thorlabs/ksc101_cs.py +0 -136
  459. egse/shutter/thorlabs/ksc101_devif.py +0 -201
  460. egse/shutter/thorlabs/ksc101_protocol.py +0 -69
  461. egse/shutter/thorlabs/ksc101_ui.py +0 -548
  462. egse/shutter/thorlabs/sc10.py +0 -82
  463. egse/shutter/thorlabs/sc10.yaml +0 -52
  464. egse/shutter/thorlabs/sc10_controller.py +0 -81
  465. egse/shutter/thorlabs/sc10_cs.py +0 -108
  466. egse/shutter/thorlabs/sc10_interface.py +0 -25
  467. egse/shutter/thorlabs/sc10_simulator.py +0 -30
  468. egse/simulator.py +0 -41
  469. egse/slack.py +0 -61
  470. egse/socketdevice.py +0 -218
  471. egse/sockets.py +0 -218
  472. egse/spw.py +0 -1479
  473. egse/stages/__init__.py +0 -12
  474. egse/stages/aerotech/ensemble.py +0 -247
  475. egse/stages/aerotech/ensemble.yaml +0 -205
  476. egse/stages/aerotech/ensemble_controller.py +0 -275
  477. egse/stages/aerotech/ensemble_cs.py +0 -110
  478. egse/stages/aerotech/ensemble_interface.py +0 -132
  479. egse/stages/aerotech/ensemble_parameters.py +0 -433
  480. egse/stages/aerotech/ensemble_simulator.py +0 -27
  481. egse/stages/aerotech/mgse_sim.py +0 -193
  482. egse/stages/arun/smd3.py +0 -111
  483. egse/stages/arun/smd3.yaml +0 -68
  484. egse/stages/arun/smd3_controller.py +0 -472
  485. egse/stages/arun/smd3_cs.py +0 -112
  486. egse/stages/arun/smd3_interface.py +0 -53
  487. egse/stages/arun/smd3_simulator.py +0 -27
  488. egse/stages/arun/smd3_stop.py +0 -16
  489. egse/stages/huber/__init__.py +0 -49
  490. egse/stages/huber/smc9300.py +0 -904
  491. egse/stages/huber/smc9300.yaml +0 -63
  492. egse/stages/huber/smc9300_cs.py +0 -178
  493. egse/stages/huber/smc9300_devif.py +0 -345
  494. egse/stages/huber/smc9300_protocol.py +0 -111
  495. egse/stages/huber/smc9300_sim.py +0 -547
  496. egse/stages/huber/smc9300_ui.py +0 -973
  497. egse/state.py +0 -173
  498. egse/statemachine.py +0 -274
  499. egse/storage/__init__.py +0 -1004
  500. egse/storage/persistence.py +0 -2295
  501. egse/storage/storage.yaml +0 -72
  502. egse/storage/storage_cs.py +0 -214
  503. egse/styles/dark.qss +0 -343
  504. egse/styles/default.qss +0 -48
  505. egse/synoptics/__init__.py +0 -412
  506. egse/synoptics/syn.yaml +0 -9
  507. egse/synoptics/syn_cs.py +0 -195
  508. egse/system.py +0 -1408
  509. egse/tcs/__init__.py +0 -14
  510. egse/tcs/tcs.py +0 -874
  511. egse/tcs/tcs.yaml +0 -14
  512. egse/tcs/tcs_cs.py +0 -202
  513. egse/tcs/tcs_devif.py +0 -292
  514. egse/tcs/tcs_protocol.py +0 -177
  515. egse/tcs/tcs_sim.py +0 -177
  516. egse/tcs/tcs_ui.py +0 -543
  517. egse/tdms.py +0 -171
  518. egse/tempcontrol/__init__.py +0 -23
  519. egse/tempcontrol/agilent/agilent34970.py +0 -109
  520. egse/tempcontrol/agilent/agilent34970.yaml +0 -44
  521. egse/tempcontrol/agilent/agilent34970_cs.py +0 -116
  522. egse/tempcontrol/agilent/agilent34970_devif.py +0 -182
  523. egse/tempcontrol/agilent/agilent34970_protocol.py +0 -99
  524. egse/tempcontrol/agilent/agilent34972.py +0 -111
  525. egse/tempcontrol/agilent/agilent34972.yaml +0 -44
  526. egse/tempcontrol/agilent/agilent34972_cs.py +0 -117
  527. egse/tempcontrol/agilent/agilent34972_devif.py +0 -189
  528. egse/tempcontrol/agilent/agilent34972_protocol.py +0 -101
  529. egse/tempcontrol/beaglebone/beaglebone.py +0 -342
  530. egse/tempcontrol/beaglebone/beaglebone.yaml +0 -110
  531. egse/tempcontrol/beaglebone/beaglebone_cs.py +0 -117
  532. egse/tempcontrol/beaglebone/beaglebone_protocol.py +0 -135
  533. egse/tempcontrol/beaglebone/beaglebone_ui.py +0 -681
  534. egse/tempcontrol/digalox/digalox.py +0 -107
  535. egse/tempcontrol/digalox/digalox.yaml +0 -36
  536. egse/tempcontrol/digalox/digalox_cs.py +0 -112
  537. egse/tempcontrol/digalox/digalox_protocol.py +0 -55
  538. egse/tempcontrol/keithley/__init__.py +0 -33
  539. egse/tempcontrol/keithley/daq6510.py +0 -662
  540. egse/tempcontrol/keithley/daq6510.yaml +0 -105
  541. egse/tempcontrol/keithley/daq6510_cs.py +0 -163
  542. egse/tempcontrol/keithley/daq6510_devif.py +0 -343
  543. egse/tempcontrol/keithley/daq6510_protocol.py +0 -78
  544. egse/tempcontrol/keithley/daq6510_sim.py +0 -186
  545. egse/tempcontrol/lakeshore/__init__.py +0 -33
  546. egse/tempcontrol/lakeshore/lsci.py +0 -361
  547. egse/tempcontrol/lakeshore/lsci.yaml +0 -162
  548. egse/tempcontrol/lakeshore/lsci_cs.py +0 -174
  549. egse/tempcontrol/lakeshore/lsci_devif.py +0 -292
  550. egse/tempcontrol/lakeshore/lsci_protocol.py +0 -73
  551. egse/tempcontrol/lakeshore/lsci_ui.py +0 -389
  552. egse/tempcontrol/ni/__init__.py +0 -0
  553. egse/tempcontrol/spid/spid.py +0 -109
  554. egse/tempcontrol/spid/spid.yaml +0 -81
  555. egse/tempcontrol/spid/spid_controller.py +0 -279
  556. egse/tempcontrol/spid/spid_cs.py +0 -136
  557. egse/tempcontrol/spid/spid_protocol.py +0 -107
  558. egse/tempcontrol/spid/spid_ui.py +0 -727
  559. egse/tempcontrol/srs/__init__.py +0 -22
  560. egse/tempcontrol/srs/ptc10.py +0 -875
  561. egse/tempcontrol/srs/ptc10.yaml +0 -227
  562. egse/tempcontrol/srs/ptc10_cs.py +0 -128
  563. egse/tempcontrol/srs/ptc10_devif.py +0 -118
  564. egse/tempcontrol/srs/ptc10_protocol.py +0 -42
  565. egse/tempcontrol/srs/ptc10_ui.py +0 -906
  566. egse/ups/apc/apc.py +0 -236
  567. egse/ups/apc/apc.yaml +0 -45
  568. egse/ups/apc/apc_cs.py +0 -101
  569. egse/ups/apc/apc_protocol.py +0 -125
  570. egse/user.yaml +0 -7
  571. egse/vacuum/beaglebone/beaglebone.py +0 -149
  572. egse/vacuum/beaglebone/beaglebone.yaml +0 -44
  573. egse/vacuum/beaglebone/beaglebone_cs.py +0 -108
  574. egse/vacuum/beaglebone/beaglebone_devif.py +0 -164
  575. egse/vacuum/beaglebone/beaglebone_protocol.py +0 -193
  576. egse/vacuum/beaglebone/beaglebone_ui.py +0 -638
  577. egse/vacuum/instrutech/igm402.py +0 -92
  578. egse/vacuum/instrutech/igm402.yaml +0 -90
  579. egse/vacuum/instrutech/igm402_controller.py +0 -128
  580. egse/vacuum/instrutech/igm402_cs.py +0 -108
  581. egse/vacuum/instrutech/igm402_interface.py +0 -49
  582. egse/vacuum/instrutech/igm402_simulator.py +0 -36
  583. egse/vacuum/keller/kellerBus.py +0 -256
  584. egse/vacuum/keller/leo3.py +0 -102
  585. egse/vacuum/keller/leo3.yaml +0 -38
  586. egse/vacuum/keller/leo3_controller.py +0 -83
  587. egse/vacuum/keller/leo3_cs.py +0 -101
  588. egse/vacuum/keller/leo3_interface.py +0 -33
  589. egse/vacuum/mks/evision.py +0 -86
  590. egse/vacuum/mks/evision.yaml +0 -75
  591. egse/vacuum/mks/evision_cs.py +0 -101
  592. egse/vacuum/mks/evision_devif.py +0 -316
  593. egse/vacuum/mks/evision_interface.py +0 -60
  594. egse/vacuum/mks/evision_simulator.py +0 -24
  595. egse/vacuum/mks/evision_ui.py +0 -704
  596. egse/vacuum/pfeiffer/acp40.py +0 -87
  597. egse/vacuum/pfeiffer/acp40.yaml +0 -60
  598. egse/vacuum/pfeiffer/acp40_controller.py +0 -117
  599. egse/vacuum/pfeiffer/acp40_cs.py +0 -109
  600. egse/vacuum/pfeiffer/acp40_interface.py +0 -40
  601. egse/vacuum/pfeiffer/acp40_simulator.py +0 -39
  602. egse/vacuum/pfeiffer/tc400.py +0 -113
  603. egse/vacuum/pfeiffer/tc400.yaml +0 -83
  604. egse/vacuum/pfeiffer/tc400_controller.py +0 -140
  605. egse/vacuum/pfeiffer/tc400_cs.py +0 -109
  606. egse/vacuum/pfeiffer/tc400_interface.py +0 -70
  607. egse/vacuum/pfeiffer/tc400_simulator.py +0 -24
  608. egse/vacuum/pfeiffer/tpg261.py +0 -81
  609. egse/vacuum/pfeiffer/tpg261.yaml +0 -66
  610. egse/vacuum/pfeiffer/tpg261_controller.py +0 -150
  611. egse/vacuum/pfeiffer/tpg261_cs.py +0 -109
  612. egse/vacuum/pfeiffer/tpg261_interface.py +0 -60
  613. egse/vacuum/pfeiffer/tpg261_simulator.py +0 -24
  614. egse/version.py +0 -174
  615. egse/visitedpositions.py +0 -398
  616. egse/windowing.py +0 -213
  617. egse/zmq/__init__.py +0 -28
  618. egse/zmq/spw.py +0 -160
  619. egse/zmq_ser.py +0 -41
  620. scripts/alerts/cold.yaml +0 -278
  621. scripts/alerts/example_alerts.yaml +0 -54
  622. scripts/alerts/transition.yaml +0 -14
  623. scripts/alerts/warm.yaml +0 -49
  624. scripts/analyse_n_fee_hk_data.py +0 -44
  625. scripts/check_hdf5_files.py +0 -192
  626. scripts/check_register_sync.py +0 -47
  627. scripts/create_hdf5_report.py +0 -295
  628. scripts/csl_model.py +0 -436
  629. scripts/csl_restore_setup.py +0 -230
  630. scripts/export-grafana-dashboards.py +0 -50
  631. scripts/fdir/cs_recovery/fdir_cs_recovery.py +0 -59
  632. scripts/fdir/fdir_table.yaml +0 -70
  633. scripts/fdir/fdir_test_recovery.py +0 -11
  634. scripts/fdir/hw_recovery/fdir_agilent_hw_recovery.py +0 -73
  635. scripts/fdir/limit_recovery/fdir_agilent_limit.py +0 -64
  636. scripts/fdir/limit_recovery/fdir_bb_heater_limit.py +0 -61
  637. scripts/fdir/limit_recovery/fdir_ensemble_limit.py +0 -33
  638. scripts/fdir/limit_recovery/fdir_pressure_limit_recovery.py +0 -71
  639. scripts/fix_csv.py +0 -80
  640. scripts/n_fee_supply_voltage_calculation.py +0 -92
  641. scripts/playground.py +0 -30
  642. scripts/print_hdf5_hk_data.py +0 -68
  643. scripts/print_register_map.py +0 -43
  644. scripts/sron/commanding/control_heaters.py +0 -44
  645. scripts/sron/commanding/pumpdown.py +0 -46
  646. scripts/sron/commanding/set_pid_setpoint.py +0 -19
  647. scripts/sron/commanding/shutdown_bbb_heaters.py +0 -10
  648. scripts/sron/commanding/shutdown_pumps.py +0 -33
  649. scripts/sron/tm_gen/tm_gen_agilent.py +0 -38
  650. scripts/sron/tm_gen/tm_gen_heaters.py +0 -4
  651. scripts/sron/tm_gen/tm_gen_spid.py +0 -13
  652. scripts/update_operational_cgse.py +0 -268
  653. scripts/update_operational_cgse_old.py +0 -273
egse/system.py DELETED
@@ -1,1408 +0,0 @@
1
- """
2
- The system module defines convenience functions that provide information on system specific
3
- functionality like, file system interactions, timing, operating system interactions, etc.
4
-
5
- The module has external dependencies to:
6
-
7
- * __distro__: for determining the Linux distribution
8
- * __psutil__: for system statistics
9
-
10
- """
11
- import builtins
12
- import collections
13
- import contextlib
14
- import datetime
15
- import functools
16
- import importlib
17
- import inspect
18
- import itertools
19
- import logging
20
- import operator
21
- import os
22
- import platform # For getting the operating system name
23
- import re
24
- import signal
25
- import socket
26
- import subprocess # For executing a shell command
27
- import sys
28
- import time
29
- from collections import namedtuple
30
- from pathlib import Path
31
- from types import FunctionType
32
- from types import ModuleType
33
- from typing import Any
34
- from typing import Callable
35
- from typing import Iterable
36
- from typing import List
37
- from typing import NamedTuple
38
- from typing import Optional
39
- from typing import Tuple
40
- from typing import Union
41
-
42
- import distro # For determining the Linux distribution
43
- import psutil
44
- from rich.text import Text
45
- from rich.tree import Tree
46
-
47
- EPOCH_1958_1970 = 378691200
48
- TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f%z"
49
-
50
- logger = logging.getLogger(__name__)
51
-
52
- from contextlib import contextmanager
53
- import logging
54
-
55
-
56
- # Code below is copied from https://gist.github.com/simon-weber/7853144
57
-
58
-
59
- @contextmanager
60
- def all_logging_disabled(highest_level=logging.CRITICAL, flag=True):
61
- """
62
- A context manager that will prevent any logging messages triggered during the body from being processed.
63
-
64
- Args:
65
- highest_level: the maximum logging level in use.
66
- This would only need to be changed if a custom level greater than CRITICAL is defined.
67
- flag: True to disable all logging [default=True]
68
- """
69
- # two kind-of hacks here:
70
- # * can't get the highest logging level in effect => delegate to the user
71
- # * can't get the current module-level override => use an undocumented
72
- # (but non-private!) interface
73
-
74
- previous_level = logging.root.manager.disable
75
-
76
- if flag:
77
- logging.disable(highest_level)
78
-
79
- try:
80
- yield
81
- finally:
82
- logging.disable(previous_level)
83
-
84
-
85
- def get_active_loggers() -> dict:
86
- return {
87
- name: logging.getLevelName(logging.getLogger(name).level) for name in sorted(logging.Logger.manager.loggerDict)
88
- }
89
-
90
-
91
- # The code below was taken from https://stackoverflow.com/a/69639238/4609203
92
-
93
-
94
- def ignore_m_warning(modules=None):
95
- """
96
- Ignore RuntimeWarning by `runpy` that occurs when executing a module with `python -m package.module`,
97
- while that module is also imported.
98
-
99
- The original warning mssage is:
100
-
101
- '<package.module>' found in sys.modules after import of package '<package'>,
102
- but prior to execution of '<package.module>'
103
- """
104
- if not isinstance(modules, (list, tuple)):
105
- modules = [modules]
106
-
107
- try:
108
- import warnings
109
- import re
110
-
111
- msg = "'{module}' found in sys.modules after import of package"
112
- for module in modules:
113
- module_msg = re.escape(msg.format(module=module))
114
- warnings.filterwarnings("ignore", message=module_msg, category=RuntimeWarning, module="runpy") # ignore -m
115
- except (ImportError, KeyError, AttributeError, Exception):
116
- pass
117
-
118
-
119
- def format_datetime(dt: Union[str, datetime.datetime] = None, fmt: str = None, width: int = 6, precision: int = 3):
120
- """Format a datetime as YYYY-mm-ddTHH:MM:SS.μs+0000.
121
-
122
- If the given argument is not timezone aware, the last part, i.e. `+0000` will not be there.
123
-
124
- If no argument is given, the timestamp is generated as
125
- `datetime.datetime.now(tz=datetime.timezone.utc)`.
126
-
127
- The `dt` argument can also be a string with the following values: today, yesterday, tomorrow,
128
- and 'day before yesterday'. The format will then be '%Y%m%d' unless specified.
129
-
130
- Optionally, a format string can be passed in to customize the formatting of the timestamp.
131
- This format string will be used with the `strftime()` method and should obey those conventions.
132
-
133
- Example:
134
- >>> format_datetime(datetime.datetime(2020, 6, 13, 14, 45, 45, 696138))
135
- '2020-06-13T14:45:45.696'
136
- >>> format_datetime(datetime.datetime(2020, 6, 13, 14, 45, 45, 696138), precision=6)
137
- '2020-06-13T14:45:45.696138'
138
- >>> format_datetime(datetime.datetime(2020, 6, 13, 14, 45, 59, 999501), precision=3)
139
- '2020-06-13T14:45:59.999'
140
- >>> format_datetime(datetime.datetime(2020, 6, 13, 14, 45, 59, 999501), precision=6)
141
- '2020-06-13T14:45:59.999501'
142
- >>> _ = format_datetime()
143
- ...
144
- >>> format_datetime("yesterday")
145
- '20220214'
146
- >>> format_datetime("yesterday", fmt="%d/%m/%Y")
147
- '14/02/2022'
148
-
149
- Args:
150
- dt (datetime): a datetime object or an agreed string like yesterday, tomorrow, ...
151
- fmt (str): a format string that is accepted by `strftime()`
152
- width (int): the width to use for formatting the microseconds
153
- precision (int): the precision for the microseconds
154
- Returns:
155
- a string representation of the current time in UTC, e.g. `2020-04-29T12:30:04.862+0000`.
156
-
157
- Raises:
158
- A ValueError will be raised when the given dt argument string is not understood.
159
- """
160
- dt = dt or datetime.datetime.now(tz=datetime.timezone.utc)
161
- if isinstance(dt, str):
162
- fmt = fmt or "%Y%m%d"
163
- if dt.lower() == "yesterday":
164
- dt = datetime.date.today() - datetime.timedelta(days=1)
165
- elif dt.lower() == "today":
166
- dt = datetime.date.today()
167
- elif dt.lower() == "day before yesterday":
168
- dt = datetime.date.today() - datetime.timedelta(days=2)
169
- elif dt.lower() == "tomorrow":
170
- dt = datetime.date.today() + datetime.timedelta(days=1)
171
- else:
172
- raise ValueError(f"Unknown date passed as an argument: {dt}")
173
-
174
- if fmt:
175
- timestamp = dt.strftime(fmt)
176
- else:
177
- width = min(width, precision)
178
- timestamp = (
179
- f"{dt.strftime('%Y-%m-%dT%H:%M')}:"
180
- f"{dt.second:02d}.{dt.microsecond//10**(6-precision):0{width}d}{dt.strftime('%z')}"
181
- )
182
-
183
- return timestamp
184
-
185
-
186
- SECONDS_IN_A_DAY = 24 * 60 * 60
187
- SECONDS_IN_AN_HOUR = 60 * 60
188
- SECONDS_IN_A_MINUTE = 60
189
-
190
-
191
- def humanize_seconds(seconds: float):
192
- """
193
- The number of seconds is represented as "[#D]d [#H]h[#M]m[#S]s.MS" where:
194
-
195
- * `#D` is the number of days if days > 0
196
- * `#H` is the number of hours if hours > 0
197
- * `#M` is the number of minutes if minutes > 0 or hours > 0
198
- * `#S` is the number of seconds
199
- * `MS` is the number of microseconds
200
-
201
- Examples:
202
- >>> humanize_seconds(20)
203
- '20s.000'
204
- >>> humanize_seconds(10*24*60*60)
205
- '10d 00s.000'
206
- >>> humanize_seconds(10*86400 + 3*3600 + 42.023)
207
- '10d 03h00m42s.023'
208
-
209
- Returns:
210
- a string representation for the number of seconds.
211
- """
212
- micro_seconds = round((seconds - int(seconds)) * 1000)
213
- rest = int(seconds)
214
-
215
- days = rest // SECONDS_IN_A_DAY
216
- rest -= SECONDS_IN_A_DAY * days
217
-
218
- hours = rest // SECONDS_IN_AN_HOUR
219
- rest -= SECONDS_IN_AN_HOUR * hours
220
-
221
- minutes = rest // SECONDS_IN_A_MINUTE
222
- rest -= SECONDS_IN_A_MINUTE * minutes
223
-
224
- seconds = rest
225
-
226
- result = ""
227
- if days:
228
- result += f"{days}d "
229
-
230
- if hours:
231
- result += f"{hours:02d}h"
232
-
233
- if minutes or hours:
234
- result += f"{minutes:02d}m"
235
-
236
- result += f"{seconds:02d}s"
237
- result += f".{micro_seconds:03d}"
238
-
239
- return result
240
-
241
-
242
- def str_to_datetime(datetime_string: str):
243
- """Convert the given string to a datetime object.
244
-
245
- Args:
246
- - datatime_string: String representing a datetime, in the format %Y-%m-%dT%H:%M:%S.%f%z.
247
-
248
- Returns: Datetime object.
249
- """
250
-
251
- return datetime.datetime.strptime(datetime_string.strip("\r"), TIME_FORMAT)
252
-
253
-
254
- def time_since_epoch_1958(datetime_string: str):
255
- """Calculate the time since epoch 1958 for the given string representation of a datetime.
256
-
257
- Args:
258
- - datetime_string: String representing a datetime, in the format %Y-%m-%dT%H:%M:%S.%f%z.
259
-
260
- Returns: Time since the 1958 epoch [s].
261
- """
262
-
263
- time_since_epoch_1970 = str_to_datetime(datetime_string).timestamp() # Since Jan 1st, 1970, midnight
264
-
265
- return time_since_epoch_1970 + EPOCH_1958_1970
266
-
267
-
268
- class Timer(object):
269
- """
270
- Context manager to benchmark some lines of code.
271
-
272
- When the context exits, the elapsed time is sent to the default logger (level=INFO).
273
-
274
- Elapsed time can be logged with the `log_elapsed()` method and requested in fractional seconds
275
- by calling the class instance. When the contexts goes out of scope, the elapsed time will not
276
- increase anymore.
277
-
278
- Log messages are sent to the logger (including egse_logger for egse.system) and the logging
279
- level can be passed in as an optional argument. Default logging level is INFO.
280
-
281
- Examples:
282
- >>> with Timer("Some calculation") as timer:
283
- ... # do some calculations
284
- ... timer.log_elapsed()
285
- ... # do some more calculations
286
- ... print(f"Elapsed seconds: {timer()}") # doctest: +ELLIPSIS
287
- Elapsed seconds: ...
288
-
289
- Args:
290
- name (str): a name for the Timer, will be printed in the logging message
291
- precision (int): the precision for the presentation of the elapsed time
292
- (number of digits behind the comma ;)
293
- log_level (int): the log level to report the timing [default=INFO]
294
-
295
- Returns:
296
- a context manager class that records the elapsed time.
297
- """
298
-
299
- def __init__(self, name="Timer", precision=3, log_level=logging.INFO):
300
- self.name = name
301
- self.precision = precision
302
- self.log_level = log_level
303
-
304
- def __enter__(self):
305
- # start is a value containing the start time in fractional seconds
306
- # end is a function which returns the time in fractional seconds
307
- self.start = time.perf_counter()
308
- self.end = time.perf_counter
309
- return self
310
-
311
- def __exit__(self, ty, val, tb):
312
- # The context goes out of scope here and we fix the elapsed time
313
- self._total_elapsed = time.perf_counter()
314
-
315
- # Overwrite self.end() so that it always returns the fixed end time
316
- self.end = self._end
317
-
318
- logger.log(self.log_level, f"{self.name}: {self.end() - self.start:0.{self.precision}f} seconds")
319
- return False
320
-
321
- def __call__(self):
322
- return self.end() - self.start
323
-
324
- def log_elapsed(self):
325
- """Sends the elapsed time info to the default logger."""
326
- logger.log(self.log_level, f"{self.name}: {self.end() - self.start:0.{self.precision}f} seconds elapsed")
327
-
328
- def get_elapsed(self) -> float:
329
- """Returns the elapsed time for this timer as a float in seconds."""
330
- return self.end() - self.start
331
-
332
- def _end(self):
333
- return self._total_elapsed
334
-
335
-
336
- def ping(host, timeout: float = 3.0):
337
- """
338
- Sends a ping request to the given host.
339
-
340
- Remember that a host may not respond to a ping (ICMP) request even if the host name is valid.
341
-
342
- Args:
343
- host (str): hostname or IP address (as a string)
344
- timeout (float): timeout in seconds
345
-
346
- Returns:
347
- True when host responds to a ping request.
348
-
349
- Reference:
350
- https://stackoverflow.com/a/32684938
351
- """
352
-
353
- # Option for the number of packets as a function of
354
- param = "-n" if platform.system().lower() == "windows" else "-c"
355
-
356
- # Building the command. Ex: "ping -c 1 google.com"
357
- command = ["ping", param, "1", host]
358
-
359
- try:
360
- return subprocess.call(command, stdout=subprocess.DEVNULL, timeout=timeout) == 0
361
- except subprocess.TimeoutExpired:
362
- logging.info(f"Ping to {host} timed out in {timeout} seconds.")
363
- return False
364
-
365
-
366
- def get_host_ip() -> Optional[str]:
367
- """Returns the IP address."""
368
-
369
- host_ip = None
370
-
371
- # The following code needs internet access
372
-
373
- try:
374
- sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
375
- # sock.connect(("8.8.8.8", 80))
376
- sock.connect(("10.255.255.255", 1))
377
- host_ip = sock.getsockname()[0]
378
- sock.close()
379
- except Exception as exc:
380
- logger.warning(f"Exception caught: {exc}")
381
-
382
- if host_ip:
383
- return host_ip
384
-
385
- # This may still return 127.0.0.1 when hostname is defined in /etc/hosts
386
- try:
387
- host_name = socket.gethostname()
388
- host_ip = socket.gethostbyname(host_name)
389
- return host_ip
390
- except (Exception,):
391
- return None
392
-
393
-
394
- def get_caller_info(level=1) -> NamedTuple:
395
- """
396
- Returns the filename, function name and lineno of the caller.
397
-
398
- The level indicates how many levels to go back in the stack.
399
- When level is 0 information about this function will be returned. That is usually not
400
- what you want so the default level is 1 which returns information about the function
401
- where the call to `get_caller_info` was made.
402
-
403
- There is no check
404
- Args:
405
- level (int): the number of levels to go back in the stack
406
-
407
- Returns:
408
- a namedtuple: CallerInfo['filename', 'function', 'lineno'].
409
- """
410
- frame = inspect.currentframe()
411
- for _ in range(level):
412
- if frame.f_back is None:
413
- break
414
- frame = frame.f_back
415
- frame_info = inspect.getframeinfo(frame)
416
-
417
- caller_info = namedtuple("CallerInfo", "filename function lineno")
418
-
419
- return caller_info(frame_info.filename, frame_info.function, frame_info.lineno)
420
-
421
-
422
- def get_referenced_var_name(obj: Any) -> List[str]:
423
- """
424
- Returns a list of variable names that reference the given object.
425
- The names can be both in the local and global namespace of the object.
426
-
427
- Args:
428
- obj (Any): object for which the variable names are returned
429
-
430
- Returns:
431
- a list of variable names.
432
- """
433
- frame = inspect.currentframe().f_back
434
- f_locals = frame.f_locals
435
- f_globals = frame.f_globals
436
- if "self" in f_locals:
437
- f_locals = frame.f_back.f_locals
438
- name_set = [k for k, v in {**f_locals, **f_globals}.items() if v is obj]
439
- return name_set or []
440
-
441
-
442
- class AttributeDict(dict):
443
- """
444
- This class is and acts like a dictionary but has the additional functionality
445
- that all keys in the dictionary are also accessible as instance attributes.
446
-
447
- >>> ad = AttributeDict({'a': 1, 'b': 2, 'c': 3})
448
-
449
- >>> assert ad.a == ad['a']
450
- >>> assert ad.b == ad['b']
451
- >>> assert ad.c == ad['c']
452
-
453
- Similarly, adding or defining attributes will make them also keys in the dict.
454
-
455
- >>> ad.d = 4 # creates a new attribute
456
- >>> print(ad['d']) # prints 4
457
- 4
458
- """
459
-
460
- def __init__(self, *args, label: str = None, **kwargs):
461
- super().__init__(*args, **kwargs)
462
- self.__dict__["_label"] = label
463
-
464
- __setattr__ = dict.__setitem__
465
- __delattr__ = dict.__delitem__
466
-
467
- def __getattr__(self, key):
468
- try:
469
- return self[key]
470
- except KeyError:
471
- raise AttributeError(key)
472
-
473
- def __rich__(self) -> Tree:
474
- label = self.__dict__["_label"] or "AttributeDict"
475
- tree = Tree(label, guide_style="dim")
476
- walk_dict_tree(self, tree, text_style="dark grey")
477
- return tree
478
-
479
- def __repr__(self):
480
- # We only want the first 10 key:value pairs
481
-
482
- count = 10
483
- sub_msg = ", ".join(f"{k!r}:{v!r}" for k, v in itertools.islice(self.items(), 0, count))
484
-
485
- # if we left out key:value pairs, print a ', ...' to indicate incompleteness
486
-
487
- return self.__class__.__name__ + f"({{{sub_msg}{', ...' if len(self) > count else ''}}})"
488
-
489
-
490
- def walk_dict_tree(dictionary: dict, tree: Tree, text_style: str = "green"):
491
- for k, v in dictionary.items():
492
- if isinstance(v, dict):
493
- branch = tree.add(f"[purple]{k}", style="", guide_style="dim")
494
- walk_dict_tree(v, branch, text_style=text_style)
495
- else:
496
- text = Text.assemble((str(k), "medium_purple1"), ": ", (str(v), text_style))
497
- tree.add(text)
498
-
499
-
500
- def recursive_dict_update(this: dict, other: dict):
501
- """
502
- Recursively update a dictionary `this` with the content of another dictionary `other`.
503
-
504
- Any key in `this` dictionary will be recursively updated with the value of the same key in the
505
- `other` dictionary.
506
-
507
- Please note that the update will be in-place, i.e. the `this` dictionaory will be
508
- changed/updated.
509
-
510
- >>> global_settings = {"A": "GA", "B": "GB", "C": "GC"}
511
- >>> local_settings = {"B": "LB", "D": "LD"}
512
- >>> {**global_settings, **local_settings}
513
- {'A': 'GA', 'B': 'LB', 'C': 'GC', 'D': 'LD'}
514
-
515
- >>> global_settings = {"A": "GA", "B": "GB", "C": "GC", "R": {"X": "GX", "Y": "GY"}}
516
- >>> local_settings = {"B": "LB", "D": "LD", "R": {"Y": "LY"}}
517
- >>> recursive_dict_update(global_settings, local_settings)
518
- {'A': 'GA', 'B': 'LB', 'C': 'GC', 'R': {'X': 'GX', 'Y': 'LY'}, 'D': 'LD'}
519
-
520
- >>> global_settings = {"A": {"B": {"C": {"D": 42}}}}
521
- >>> local_settings = {"A": {"B": {"C": 13, "D": 73}}}
522
- >>> recursive_dict_update(global_settings, local_settings)
523
- {'A': {'B': {'C': 13, 'D': 73}}}
524
-
525
- Args:
526
- this (dict): The origin dictionary
527
- other (dict): Changes that shall be applied to `this`
528
-
529
- Returns:
530
- A new dictionary with the recursive updates.
531
- """
532
-
533
- if not isinstance(this, dict) or not isinstance(other, dict):
534
- raise ValueError("Expected arguments of type dict.")
535
-
536
- for key, value in other.items():
537
- if isinstance(value, dict) and isinstance(this.get(key), dict):
538
- this[key] = recursive_dict_update(this[key], other[key])
539
- else:
540
- this[key] = other[key]
541
-
542
- return this
543
-
544
-
545
- def flatten_dict(source_dict: dict):
546
- """
547
- Flatten the given dictionary concatenating the keys with a colon ':'.
548
-
549
- >>> d = {"A": 1, "B": {"E": {"F": 2}}, "C": {"D": 3}}
550
- >>> flatten_dict(d)
551
- {'A': 1, 'B:E:F': 2, 'C:D': 3}
552
-
553
- >>> d = {"A": 'a', "B": {"C": {"D": 'd', "E": 'e'}, "F": 'f'}}
554
- >>> flatten_dict(d)
555
- {'A': 'a', 'B:C:D': 'd', 'B:C:E': 'e', 'B:F': 'f'}
556
-
557
- Args:
558
- source_dict: the original dictionary that will be flattened
559
-
560
- Returns:
561
- A new flattened dictionary.
562
- """
563
-
564
- def expand(key, value):
565
- if isinstance(value, dict):
566
- return [(key + ":" + k, v) for k, v in flatten_dict(value).items()]
567
- else:
568
- return [(key, value)]
569
-
570
- items = [item for k, v in source_dict.items() for item in expand(k, v)]
571
-
572
- return dict(items)
573
-
574
-
575
- def get_system_stats():
576
- """
577
- Gather system information about the CPUs and memory usage and return a dictionary with the
578
- following information:
579
-
580
- * cpu_load: load average over a period of 1, 5,and 15 minutes given in in percentage
581
- (i.e. related to the number of CPU cores that are installed on your system) [percentage]
582
- * cpu_count: physical and logical CPU count, i.e. the number of CPU cores (incl. hyper-threads)
583
- * total_ram: total physical ram available [bytes]
584
- * avail_ram: the memory that can be given instantly to processes without the system going
585
- into swap. This is calculated by summing different memory values depending on the platform
586
- [bytes]
587
- * boot_time: the system boot time expressed in seconds since the epoch [s]
588
- * since: boot time of the system, aka Up time [str]
589
-
590
- Returns:
591
- a dictionary with CPU and memory statistics.
592
- """
593
- statistics = {}
594
-
595
- # Get Physical and Logical CPU Count
596
-
597
- physical_and_logical_cpu_count = psutil.cpu_count()
598
- statistics["cpu_count"] = physical_and_logical_cpu_count
599
-
600
- # Load average
601
- # This is the average system load calculated over a given period of time of 1, 5 and 15 minutes.
602
- #
603
- # The numbers returned by psutil.getloadavg() only make sense if
604
- # related to the number of CPU cores installed on the system.
605
- #
606
- # Here we are converting the load average into percentage.
607
- # The higher the percentage the higher the load.
608
-
609
- cpu_load = [x / physical_and_logical_cpu_count * 100 for x in psutil.getloadavg()]
610
- statistics["cpu_load"] = cpu_load
611
-
612
- # Memory usage
613
-
614
- vmem = psutil.virtual_memory()
615
-
616
- statistics["total_ram"] = vmem.total
617
- statistics["avail_ram"] = vmem.available
618
-
619
- # boot_time = seconds since the epoch timezone
620
- # the Unix epoch is 00:00:00 UTC on 1 January 1970.
621
-
622
- boot_time = psutil.boot_time()
623
- statistics["boot_time"] = boot_time
624
- statistics["since"] = datetime.datetime.fromtimestamp(boot_time, tz=datetime.timezone.utc).strftime(
625
- "%Y-%m-%d %H:%M:%S"
626
- )
627
-
628
- return statistics
629
-
630
-
631
- def get_system_name() -> str:
632
- """Returns the name of the system in lower case.
633
-
634
- Returns:
635
- name: 'linux', 'darwin', 'windows', ...
636
- """
637
- return platform.system().lower()
638
-
639
-
640
- def get_os_name() -> str:
641
- """Returns the name of the OS in lower case.
642
-
643
- If no name could be determined, 'unknown' is returned.
644
-
645
- Returns:
646
- os: 'macos', 'centos'
647
- """
648
- sys_name = get_system_name()
649
- if sys_name == "darwin":
650
- return "macos"
651
- if sys_name == "linux":
652
- return distro.id().lower()
653
- if sys_name == "windows":
654
- return "windows"
655
- return "unknown"
656
-
657
-
658
- def get_os_version() -> str:
659
- """Return the version of the OS.
660
-
661
- If no version could be determined, 'unknown' is returned.
662
-
663
- Returns:
664
- version: as '10.15' or '8.0' or 'unknown'
665
- """
666
-
667
- # Don't use `distro.version()` to get the macOS version. That function will return the version
668
- # of the Darwin kernel.
669
-
670
- os_name = get_os_name()
671
- sys_name = get_system_name()
672
- if os_name == "unknown":
673
- return "unknown"
674
- if os_name == "macos":
675
- version, _, _ = platform.mac_ver()
676
- return ".".join(version.split(".")[:2])
677
- if sys_name == "linux":
678
- return distro.version()
679
-
680
- # FIXME: add other OS here for their version number
681
-
682
- return "unknown"
683
-
684
-
685
- def wait_until(condition, *args, interval=0.1, timeout=1, verbose=False, **kwargs) -> int:
686
- """
687
- Sleep until the given condition is fulfilled. The arguments are passed into the condition
688
- callable which is called in a while loop until the condition is met or the timeout is reached.
689
-
690
- Note that the condition can be a function, method or callable class object.
691
- An example of the latter is:
692
-
693
- class SleepUntilCount:
694
- def __init__(self, end):
695
- self._end = end
696
- self._count = 0
697
-
698
- def __call__(self, *args, **kwargs):
699
- self._count += 1
700
- if self._count >= self._end:
701
- return True
702
- else:
703
- return False
704
-
705
-
706
- Args:
707
- condition: a callable that returns True when the condition is met, False otherwise
708
- interval: the sleep interval between condition checks [s, default=0.1]
709
- timeout: the period after which the function returns, even when the condition is
710
- not met [s, default=1]
711
- verbose: log debugging messages if True
712
- *args: any arguments that will be passed into the condition function
713
-
714
- Returns:
715
- True when function timed out, False otherwise
716
- """
717
-
718
- if inspect.isfunction(condition) or inspect.ismethod(condition):
719
- func_name = condition.__name__
720
- else:
721
- func_name = condition.__class__.__name__
722
-
723
- caller = get_caller_info(level=2)
724
-
725
- start = time.time()
726
-
727
- while not condition(*args, **kwargs):
728
- if time.time() - start > timeout:
729
- logger.warning(
730
- f"Timeout after {timeout} sec, from {caller.filename} at {caller.lineno},"
731
- f" {func_name}{args} not met."
732
- )
733
- return True
734
- time.sleep(interval)
735
-
736
- if verbose:
737
- logger.debug(f"wait_until finished successfully, {func_name}{args}{kwargs} is met.")
738
-
739
- return False
740
-
741
-
742
- def waiting_for(condition, *args, interval=0.1, timeout=1, verbose=False, **kwargs):
743
- """
744
- Sleep until the given condition is fulfilled. The arguments are passed into the condition
745
- callable which is called in a while loop until the condition is met or the timeout is reached.
746
-
747
- Note that the condition can be a function, method or callable class object.
748
- An example of the latter is:
749
-
750
- class SleepUntilCount:
751
- def __init__(self, end):
752
- self._end = end
753
- self._count = 0
754
-
755
- def __call__(self, *args, **kwargs):
756
- self._count += 1
757
- if self._count >= self._end:
758
- return True
759
- else:
760
- return False
761
-
762
-
763
- Args:
764
- condition: a callable that returns True when the condition is met, False otherwise
765
- interval: the sleep interval between condition checks [s, default=0.1]
766
- timeout: the period after which the function returns, even when the condition is
767
- not met [s, default=1]
768
- verbose: log debugging messages if True
769
- *args: any arguments that will be passed into the condition function
770
-
771
- Raises:
772
- A TimeoutError when the condition was not fulfilled within the timeout period.
773
- """
774
-
775
- if inspect.isfunction(condition) or inspect.ismethod(condition):
776
- func_name = condition.__name__
777
- else:
778
- func_name = condition.__class__.__name__
779
-
780
- caller = get_caller_info(level=2)
781
-
782
- start = time.time()
783
-
784
- while not condition(*args, **kwargs):
785
- if time.time() - start > timeout:
786
- raise TimeoutError(
787
- f"Timeout after {timeout} sec, from {caller.filename} at {caller.lineno},"
788
- f" {func_name}{args} not met."
789
- )
790
- time.sleep(interval)
791
-
792
- duration = time.time() - start
793
-
794
- if verbose:
795
- logger.debug(f"waiting_for finished successfully after {duration:.3f}s, {func_name}{args}{kwargs} is met.")
796
-
797
- return duration
798
-
799
-
800
- def has_internet(host="8.8.8.8", port=53, timeout=3):
801
- """Returns True if we have internet connection.
802
-
803
- Host: 8.8.8.8 (google-public-dns-a.google.com)
804
- OpenPort: 53/tcp
805
- Service: domain (DNS/TCP)
806
-
807
- .. Note::
808
-
809
- This might give the following error codes:
810
-
811
- * [Errno 51] Network is unreachable
812
- * [Errno 61] Connection refused (because the port is blocked?)
813
- * timed out
814
-
815
- Source: https://stackoverflow.com/a/33117579
816
- """
817
- try:
818
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
819
- s.settimeout(timeout)
820
- s.connect((host, port))
821
- return True
822
- except socket.error as ex:
823
- logging.info(f"No Internet: Unable to open socket to {host}:{port} [{ex}]")
824
- return False
825
- finally:
826
- if s is not None:
827
- s.close()
828
-
829
-
830
- def do_every(period: float, func: callable, *args) -> None:
831
- """
832
-
833
- This method executes a function periodically, taking into account
834
- that the function that is executed will take time also and using a
835
- simple `sleep()` will cause a drift. This method will not drift.
836
-
837
- You can use this function in combination with the threading module to execute the
838
- function in the background, but be careful as the function might not be thread safe.
839
-
840
- ```
841
- timer_thread = threading.Thread(target=do_every, args=(10, func))
842
- timer_thread.daemon = True
843
- timer_thread.start()
844
- ```
845
-
846
- Args:
847
- period: a time interval between successive executions [seconds]
848
- func: the function to be executed
849
- *args: optional arguments to be passed to the function
850
- """
851
-
852
- # Code from SO:https://stackoverflow.com/a/28034554/4609203
853
- # The max in the yield line serves to protect sleep from negative numbers in case the
854
- # function being called takes longer than the period specified. In that case it would
855
- # execute immediately and make up the lost time in the timing of the next execution.
856
-
857
- def g_tick():
858
- next_time = time.time()
859
- while True:
860
- next_time += period
861
- yield max(next_time - time.time(), 0)
862
-
863
- g = g_tick()
864
- while True:
865
- time.sleep(next(g))
866
- func(*args)
867
-
868
-
869
- @contextlib.contextmanager
870
- def chdir(dirname=None):
871
- """
872
- Context manager to temporarily change directory.
873
-
874
- Args:
875
- dirname (str or Path): temporary folder name to switch to within the context
876
-
877
- Examples:
878
-
879
- >>> with chdir('/tmp'):
880
- ... # do stuff in this writable tmp folder
881
- ... pass
882
-
883
- """
884
- current_dir = os.getcwd()
885
- try:
886
- if dirname is not None:
887
- os.chdir(dirname)
888
- yield
889
- finally:
890
- os.chdir(current_dir)
891
-
892
-
893
- @contextlib.contextmanager
894
- def env_var(**kwargs):
895
- """
896
- Context manager to run some code that need alternate settings for environment variables.
897
-
898
- Args:
899
- **kwargs: dictionary with environment variables that are needed
900
-
901
- Examples:
902
-
903
- >>> with env_var(PLATO_DATA_STORAGE_LOCATION="/Users/rik/data"):
904
- ... # do stuff that needs these alternate setting
905
- ... pass
906
-
907
- """
908
- saved_env = {}
909
- try:
910
- for k, v in kwargs.items():
911
- saved_env[k] = os.environ.get(k)
912
- os.environ[k] = v
913
- yield
914
- finally:
915
- for k, v in saved_env.items():
916
- if v is not None:
917
- os.environ[k] = v
918
- else:
919
- del os.environ[k]
920
-
921
-
922
- def filter_by_attr(elements: Iterable, **attrs) -> List:
923
- """
924
- A helper that returns the elements from the iterable that meet all the traits passed in `attrs`.
925
-
926
- The attributes are compared to their value with the `operator.eq` function. However,
927
- when the given value for an attribute is a tuple, the first element in the tuple is
928
- considered a comparison function and the second value the actual value. The attribute
929
- is then compared to the value using this function.
930
-
931
- ```
932
- result = filter_by_attr(setups, camera__model="EM", site_id=(is_in, ("CSL", "INTA")))
933
- ```
934
- The function `is_in` is defined as follows:
935
- ```
936
- def is_in(a, b):
937
- return a in b
938
- ```
939
- but you can of course also use a lambda function: `lambda a, b: a in b`.
940
-
941
- One function is treated special, it is the built-in function `hasattr`. Using this function,
942
- the value can be `True` or `False`. Use this to return all elements in the iterable
943
- that have the attribute, or not. The following example returns all Setups where the
944
- `gse.ogse.fwc_factor` is not defined:
945
- ```
946
- result = filter_by_attr(setups, camera__model="EM", gse__ogse__fwc_factor=(hasattr, False)))
947
- ```
948
-
949
- When multiple attributes are specified, they are checked using logical AND, not logical OR.
950
- Meaning they have to meet every attribute passed in and not one of them.
951
-
952
- To have a nested attribute search (i.e. search by `gse.hexapod.ID`) then
953
- pass in `gse__hexapod__ID` as the keyword argument.
954
-
955
- If nothing is found that matches the attributes passed, then an empty list is returned.
956
-
957
- When an attribute is not part of the iterated object, that attribute is silently ignored.
958
-
959
- Args:
960
- elements: An iterable to search through.
961
- attrs: Keyword arguments that denote attributes to search with.
962
- """
963
-
964
- # This code is based on and originates from the get(iterable, **attr) function in the
965
- # discord/utils.py package (https://github.com/Rapptz/discord.py). After my own version,
966
- # Ruud van der Ham, improved the code drastically to the version it is now.
967
-
968
- def check(attr_, func, value_, el):
969
- try:
970
- a = operator.attrgetter(attr_)(el)
971
- return value_ if func is hasattr else func(a, value_)
972
- except AttributeError:
973
- return not value_ if func is hasattr else False
974
-
975
- attr_func_values = []
976
- for attr, value in attrs.items():
977
- if not (isinstance(value, (tuple, list)) and len(value) == 2 and callable(value[0])):
978
- value = (operator.eq, value)
979
- attr_func_values.append((attr.replace("__", "."), *value))
980
-
981
- return [el for el in elements if all(check(attr, func, value, el) for attr, func, value in attr_func_values)]
982
-
983
-
984
- def replace_environment_variable(input_string: str):
985
- """Returns the `input_string` with all occurrences of ENV['var'].
986
-
987
- >>> replace_environment_variable("ENV['HOME']/data/CSL")
988
- '/Users/rik/data/CSL'
989
-
990
- Args:
991
- input_string (str): the string to replace
992
- Returns:
993
- The input string with the ENV['var'] replaced, or None when the environment variable
994
- doesn't exists.
995
- """
996
-
997
- match = re.search(r"(.*)ENV\[['\"](\w+)['\"]\](.*)", input_string)
998
- if not match:
999
- return input_string
1000
- pre_match = match.group(1)
1001
- var = match.group(2)
1002
- post_match = match.group(3)
1003
-
1004
- result = os.getenv(var, None)
1005
-
1006
- return pre_match + result + post_match if result else None
1007
-
1008
-
1009
- def read_last_line(filename: str, max_line_length=5000):
1010
- """Returns the last line of a (text) file.
1011
-
1012
- The argument `max_line_length` should be at least the length of the last line in the file,
1013
- because this value is used to backtrack from the end of the file as an optimization.
1014
-
1015
- Args:
1016
- filename (Option[PurePath, str]): the filename as a string or Path
1017
- max_line_length (int): the maximum length of the lines in the file
1018
- Returns:
1019
- The last line in the file (whitespace stripped from the right). An empty string is returned
1020
- when the file is empty, `None` is returned when the file doesn't exist.
1021
- """
1022
- filename = Path(filename)
1023
-
1024
- if not filename.exists():
1025
- return None
1026
-
1027
- with filename.open("rb") as file:
1028
- file.seek(0, 2) # 2 is relative to end of file
1029
- size = file.tell()
1030
- if size:
1031
- file.seek(max(0, size - max_line_length))
1032
- return file.readlines()[-1].decode("utf-8").rstrip("\n")
1033
- else:
1034
- return ""
1035
-
1036
-
1037
- def read_last_lines(filename: str, num_lines: int):
1038
- """Return the last lines of a text file.
1039
-
1040
- Args:
1041
- - filename: Filename.
1042
- - num_lines: Number of lines at the back of the file that should be read and returned.
1043
-
1044
- Returns: Last lines of a text file.
1045
- """
1046
-
1047
- # See: https://www.geeksforgeeks.org/python-reading-last-n-lines-of-a-file/
1048
- # (Method 3: Through exponential search)
1049
-
1050
- filename = Path(filename)
1051
-
1052
- assert num_lines > 1
1053
-
1054
- if not filename.exists():
1055
- return None
1056
-
1057
- assert num_lines >= 0
1058
-
1059
- # Declaring variable to implement exponential search
1060
-
1061
- pos = num_lines + 1
1062
-
1063
- # List to store last N lines
1064
-
1065
- lines = []
1066
-
1067
- with open(filename) as f:
1068
- while len(lines) <= num_lines:
1069
- try:
1070
- f.seek(-pos, 2)
1071
-
1072
- except IOError:
1073
- f.seek(0)
1074
- break
1075
-
1076
- finally:
1077
- lines = list(f)
1078
-
1079
- # Increasing value of variable exponentially
1080
-
1081
- pos *= 2
1082
-
1083
- return lines[-num_lines:]
1084
-
1085
-
1086
- def get_module_location(arg) -> Optional[Path]:
1087
- """
1088
- Returns the location of the module as a Path object.
1089
-
1090
- The function can be given a string, which should then be a module name, or a function or module.
1091
- For the latter two, the module name will be determined.
1092
-
1093
- >>> get_module_location('egse')
1094
- >>> get_module_location(egse.system)
1095
- >>> get_module_location()
1096
-
1097
- Args:
1098
- arg: can be one of the following: function, module, string
1099
-
1100
- Returns:
1101
- The location of the module as a Path object or None when the location can not be determined or
1102
- an invalid argument was provided.
1103
- """
1104
- if isinstance(arg, FunctionType):
1105
- module_name = arg.__module__
1106
- elif isinstance(arg, ModuleType):
1107
- module_name = arg.__name__
1108
- elif isinstance(arg, str):
1109
- module_name = arg
1110
- else:
1111
- return None
1112
-
1113
- try:
1114
- module = importlib.import_module(module_name)
1115
- except TypeError as exc:
1116
- return None
1117
-
1118
- return Path(module.__file__).parent.resolve()
1119
-
1120
-
1121
- def get_full_classname(obj: object) -> str:
1122
- """Returns the fully qualified class name for this object."""
1123
-
1124
- # Take into account that obj might be a class or a builtin or even a
1125
- # literal like an int or a float or a complex number
1126
-
1127
- if type(obj) is type or obj.__class__.__module__ == str.__module__:
1128
- try:
1129
- module = obj.__module__
1130
- name = obj.__qualname__
1131
- except (TypeError, AttributeError):
1132
- module = type(obj).__module__
1133
- name = obj.__class__.__qualname__
1134
- else:
1135
- module = obj.__class__.__module__
1136
- name = obj.__class__.__qualname__
1137
-
1138
- return module + "." + name
1139
-
1140
-
1141
- def find_class(class_name: str):
1142
- """Find and returns a class based on the fully qualified name.
1143
-
1144
- A class name can be preceded with the string `class//`. This is used in YAML
1145
- files where the class is then instantiated on load.
1146
-
1147
- Args:
1148
- class_name (str): a fully qualified name for the class
1149
- Returns:
1150
- The class object corresponding to the fully qualified class name.
1151
- Raises:
1152
- AttributeError: when the class is not found in the module.
1153
- ValueError: when the class_name can not be parsed.
1154
- ModuleNotFoundError: if the module could not be found.
1155
- """
1156
- if class_name.startswith("class//"):
1157
- class_name = class_name[7:]
1158
-
1159
- module_name, class_name = class_name.rsplit(".", 1)
1160
- module = importlib.import_module(module_name)
1161
- return getattr(module, class_name)
1162
-
1163
-
1164
- def type_name(var):
1165
- """Returns the name of the type of var."""
1166
- return type(var).__name__
1167
-
1168
-
1169
- def check_argument_type(obj: object, name: str, target_class: Union[type, Tuple[type]], allow_none: bool = False):
1170
- """Check that the given object is of a specific (sub)type of the given target_class.
1171
-
1172
- The target_class can be a tuple of types.
1173
-
1174
- Raises:
1175
- TypeError when not of the required type or None when not allowed.
1176
- """
1177
- if obj is None and allow_none:
1178
- return
1179
- if obj is None:
1180
- raise TypeError(f"The argument '{name}' cannot be None.")
1181
- if not isinstance(obj, target_class):
1182
- raise TypeError(f"The argument '{name}' must be of type {target_class}, but is {type(obj)}")
1183
-
1184
-
1185
- def check_str_for_slash(arg: str):
1186
- """Check if there is a slash in the given string, and raise a ValueError if so."""
1187
-
1188
- if "/" in arg:
1189
- ValueError(f"The given argument can not contain slashes, {arg=}.")
1190
-
1191
-
1192
- def check_is_a_string(var, allow_none=False):
1193
- """Calls is_a_string and raises a type error if the check fails."""
1194
- if var is None and allow_none:
1195
- return
1196
- if var is None and not allow_none:
1197
- raise TypeError("The given variable cannot be None.")
1198
- if not isinstance(var, str):
1199
- raise TypeError(f"var must be a string, however {type(var)=}")
1200
-
1201
-
1202
- def sanity_check(flag: bool, msg: str):
1203
- """
1204
- This is a replacement for the 'assert' statement. Use this in production code
1205
- such that your checks are not removed during optimisations.
1206
- """
1207
- if not flag:
1208
- raise AssertionError(msg)
1209
-
1210
-
1211
- class NotSpecified:
1212
- """Class for NOT_SPECIFIED constant.
1213
- Is used so that a parameter can have a default value other than None.
1214
-
1215
- Evaluate to False when converted to boolean.
1216
- """
1217
-
1218
- def __nonzero__(self):
1219
- """Always returns False. Called when to converting to bool in Python 2."""
1220
- return False
1221
-
1222
- def __bool__(self):
1223
- """Always returns False. Called when to converting to bool in Python 3."""
1224
- return False
1225
-
1226
-
1227
- NOT_SPECIFIED = NotSpecified()
1228
-
1229
- # Do not try to catch SIGKILL (9) that will just terminate your script without any warning
1230
-
1231
- SIGNAL_NAME = {
1232
- 1: "SIGHUP",
1233
- 2: "SIGINT",
1234
- 3: "SIGQUIT",
1235
- 6: "SIGABRT",
1236
- 15: "SIGTERM",
1237
- 30: "SIGUSR1",
1238
- 31: "SIGUSR2",
1239
- }
1240
-
1241
-
1242
- class SignalCatcher:
1243
- """
1244
- This class registers handler to signals. When a signal is caught, the handler is
1245
- executed and a flag for termination or user action is set to True. Check for this
1246
- flag in your application loop.
1247
-
1248
- Termination signals: 1 HUP, 2 INT, 3 QUIT, 6 ABORT, 15 TERM
1249
- User signals: 30 USR1, 31 USR2
1250
- """
1251
-
1252
- def __init__(self):
1253
- self.term_signal_received = False
1254
- self.user_signal_received = False
1255
- self.term_signals = [1, 2, 3, 6, 15]
1256
- self.user_signals = [30, 31]
1257
- for signal_number in self.term_signals:
1258
- signal.signal(signal_number, self.handler)
1259
- for signal_number in self.user_signals:
1260
- signal.signal(signal_number, self.handler)
1261
-
1262
- self._signal_number = None
1263
- self._signal_name = None
1264
-
1265
- @property
1266
- def signal_number(self):
1267
- return self._signal_number
1268
-
1269
- @property
1270
- def signal_name(self):
1271
- return self._signal_name
1272
-
1273
- def handler(self, signal_number, frame):
1274
- """Handle the known signals by setting the appropriate flag."""
1275
- logger.warning(f"Received signal {SIGNAL_NAME[signal_number]} [{signal_number}].")
1276
- if signal_number in self.term_signals:
1277
- self.term_signal_received = True
1278
- if signal_number in self.user_signals:
1279
- self.user_signal_received = True
1280
- self._signal_number = signal_number
1281
- self._signal_name = SIGNAL_NAME[signal_number]
1282
-
1283
- def clear(self, term: bool = False):
1284
- """
1285
- Call this method to clear the user signal after handling.
1286
- Termination signals are not cleared by default since the application is supposed to terminate.
1287
- Pass in a `term=True` to also clear the TERM signals, e.g. when you want to ignore some
1288
- TERM signals.
1289
- """
1290
- self.user_signal_received = False
1291
- if term:
1292
- self.term_signal_received = False
1293
- self._signal_number = None
1294
- self._signal_name = None
1295
-
1296
-
1297
- def is_in(a, b):
1298
- """Returns result of `a in b`."""
1299
- return a in b
1300
-
1301
-
1302
- def is_not_in(a, b):
1303
- """Returns result of `a not in b`."""
1304
- return a not in b
1305
-
1306
-
1307
- def is_in_ipython():
1308
- """Returns True if the code is running in IPython."""
1309
- return hasattr(builtins, "__IPYTHON__")
1310
-
1311
-
1312
- _function_timing = {}
1313
-
1314
-
1315
- def execution_time(func):
1316
- """
1317
- A decorator to save the execution time of the function. Use this decorator
1318
- if you want —by default and always— have an idea of the average execution time
1319
- of the given function.
1320
-
1321
- Use this in conjunction with the `get_average_execution_time()` function to
1322
- retrieve the average execution time for the given function.
1323
- """
1324
-
1325
- @functools.wraps(func)
1326
- def wrapper(*args, **kwargs):
1327
- return save_average_execution_time(func, *args, **kwargs)
1328
-
1329
- return wrapper
1330
-
1331
-
1332
- def save_average_execution_time(func: Callable, *args, **kwargs):
1333
- """
1334
- Executes the function 'func' with the given arguments and saves the execution time. All positional
1335
- arguments (in args) and keyword arguments (in kwargs) are passed into the function. The execution
1336
- time is saved in a deque of maximum 100 elements. When more times are added, the oldest times are
1337
- discarded. This function is used in conjunction with the `get_average_execution_time()` function.
1338
- """
1339
-
1340
- with Timer(log_level=logging.NOTSET) as timer:
1341
- response = func(*args, **kwargs)
1342
-
1343
- if func not in _function_timing:
1344
- _function_timing[func] = collections.deque(maxlen=100)
1345
-
1346
- _function_timing[func].append(timer.get_elapsed())
1347
-
1348
- return response
1349
-
1350
-
1351
- def get_average_execution_time(func: Callable) -> float:
1352
- """
1353
- Returns the average execution time of the given function. The function 'func' shall be previously executed using
1354
- the `save_average_execution_time()` function which remembers the last 100 execution times of the function.
1355
- You can also decorate your function with `@execution_time` to permanently monitor it.
1356
- The average time is a moving average over the last 100 times. If the function was never called before, 0.0 is
1357
- returned.
1358
-
1359
- This function can be used when setting a frequency to execute a certain function. When the average execution time
1360
- of the function is longer than the execution interval, the frequency shall be decreased or the process will get
1361
- stalled.
1362
- """
1363
-
1364
- # If the function was previously wrapped with the `@execution_time` wrapper, we need to get
1365
- # to the original function object because that's the one that is saved.
1366
-
1367
- with contextlib.suppress(AttributeError):
1368
- func = func.__wrapped__
1369
-
1370
- try:
1371
- d = _function_timing[func]
1372
- return sum(d) / len(d)
1373
- except KeyError:
1374
- return 0.0
1375
-
1376
-
1377
- def get_average_execution_times() -> dict:
1378
- """
1379
- Returns a dictionary with "function name": average execution time, for all function that have been
1380
- monitored in this process.
1381
- """
1382
- return {func.__name__: get_average_execution_time(func) for func in _function_timing}
1383
-
1384
-
1385
- def clear_average_execution_times():
1386
- """Clear out all function timing for this process."""
1387
- _function_timing.clear()
1388
-
1389
-
1390
- def get_system_architecture() -> str:
1391
- """
1392
- Returns the machine type. This is a string describing the processor architecture,
1393
- like i386 or arm64, but the exact string is not defined. An empty string can be returned when
1394
- the type cannot be determined.
1395
- """
1396
- return platform.machine()
1397
-
1398
-
1399
- ignore_m_warning("egse.system")
1400
-
1401
- if __name__ == "__main__":
1402
- print(f"Host IP: {get_host_ip()}")
1403
- print(f"System name: {get_system_name()}")
1404
- print(f"OS name: {get_os_name()}")
1405
- print(f"OS version: {get_os_version()}")
1406
- print(f"Architecture: {get_system_architecture()}")
1407
- print(f"Python version: {sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")
1408
- print("Running in IPython") if is_in_ipython() else None