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