pyobs-core 1.52.16__tar.gz → 2.0.0.dev4__tar.gz

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 (456) hide show
  1. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/PKG-INFO +2 -1
  2. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/application.py +52 -8
  3. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/background_task.py +7 -0
  4. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/cli/pyobs.py +6 -0
  5. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/comm/comm.py +199 -21
  6. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/comm/commlogging.py +5 -2
  7. pyobs_core-2.0.0.dev4/pyobs/comm/local/localcomm.py +157 -0
  8. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/comm/proxy.py +63 -1
  9. pyobs_core-2.0.0.dev4/pyobs/comm/xmpp/rpc.py +303 -0
  10. pyobs_core-2.0.0.dev4/pyobs/comm/xmpp/serializer.py +338 -0
  11. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/comm/xmpp/xep_0009_timeout/rpc.py +6 -19
  12. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/comm/xmpp/xmppclient.py +0 -1
  13. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/comm/xmpp/xmppcomm.py +319 -6
  14. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/event.py +2 -1
  15. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/log.py +18 -2
  16. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/modules/getfitsheaders.py +2 -4
  17. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/offsets/spilled_light.py +3 -3
  18. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IAutoFocus.py +5 -2
  19. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IBinning.py +16 -19
  20. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IConfig.py +6 -24
  21. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/ICooling.py +15 -14
  22. pyobs_core-2.0.0.dev4/pyobs/interfaces/IExposure.py +29 -0
  23. pyobs_core-2.0.0.dev4/pyobs/interfaces/IExposureTime.py +38 -0
  24. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IFilters.py +13 -17
  25. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IFocuser.py +14 -19
  26. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IGain.py +14 -19
  27. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IImageFormat.py +15 -19
  28. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IImageType.py +13 -10
  29. pyobs_core-2.0.0.dev4/pyobs/interfaces/IMode.py +43 -0
  30. pyobs_core-2.0.0.dev4/pyobs/interfaces/IModule.py +26 -0
  31. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IMotion.py +18 -13
  32. pyobs_core-2.0.0.dev4/pyobs/interfaces/IMultiFiber.py +48 -0
  33. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IOffsetsAltAz.py +17 -12
  34. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IOffsetsRaDec.py +17 -12
  35. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IPointingAltAz.py +17 -12
  36. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IPointingHGS.py +17 -12
  37. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IPointingHelioprojective.py +17 -12
  38. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IPointingRaDec.py +17 -12
  39. pyobs_core-2.0.0.dev4/pyobs/interfaces/IReady.py +24 -0
  40. pyobs_core-2.0.0.dev4/pyobs/interfaces/IRotation.py +31 -0
  41. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IRunning.py +13 -1
  42. pyobs_core-2.0.0.dev4/pyobs/interfaces/ITemperatures.py +32 -0
  43. pyobs_core-2.0.0.dev4/pyobs/interfaces/IVideo.py +19 -0
  44. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IWindow.py +16 -17
  45. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/__init__.py +48 -24
  46. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/interface.py +4 -0
  47. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/mixins/camerasettings.py +3 -6
  48. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/mixins/fitsheader.py +14 -11
  49. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/mixins/follow.py +3 -4
  50. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/mixins/motionstatus.py +9 -12
  51. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/mixins/waitformotion.py +12 -12
  52. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/mixins/weatheraware.py +3 -5
  53. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/camera/basecamera.py +36 -38
  54. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/camera/basespectrograph.py +0 -8
  55. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/camera/basevideo.py +5 -21
  56. pyobs_core-2.0.0.dev4/pyobs/modules/camera/dummycamera.py +346 -0
  57. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/camera/dummyspectrograph.py +0 -11
  58. pyobs_core-2.0.0.dev4/pyobs/modules/camera/dummyvideo.py +69 -0
  59. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/camera/pipelinecamera.py +1 -10
  60. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/flatfield/flatfield.py +49 -63
  61. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/flatfield/pointing.py +5 -10
  62. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/flatfield/scheduler.py +11 -15
  63. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/focus/focusmodel.py +20 -35
  64. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/focus/focusseries.py +52 -56
  65. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/module.py +117 -4
  66. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/pointing/_base.py +2 -6
  67. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/pointing/_baseguiding.py +8 -9
  68. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/pointing/acquisition.py +37 -38
  69. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/pointing/autoguiding.py +19 -28
  70. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/pointing/scienceframeguiding.py +0 -16
  71. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/robotic/pointing.py +7 -8
  72. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/roof/baseroof.py +8 -11
  73. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/roof/dummyroof.py +3 -0
  74. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/telescope/basetelescope.py +16 -27
  75. pyobs_core-2.0.0.dev4/pyobs/modules/telescope/dummytelescope.py +312 -0
  76. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/utils/autonomouswarning.py +17 -13
  77. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/utils/dummymode.py +12 -35
  78. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/utils/kiosk.py +19 -19
  79. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/utils/stellarium.py +7 -8
  80. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/utils/telegram.py +10 -13
  81. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/utils/trigger.py +2 -4
  82. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/weather/weather_api.py +1 -1
  83. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/object.py +21 -9
  84. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/merits/transit.py +5 -1
  85. pyobs_core-2.0.0.dev4/pyobs/robotic/scheduler/targets/__init__.py +6 -0
  86. pyobs_core-2.0.0.dev4/pyobs/robotic/scheduler/targets/helioprojectiveradialarget.py +32 -0
  87. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scripts/calibration/darkbias.py +13 -16
  88. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scripts/calibration/pointing.py +9 -12
  89. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scripts/calibration/skyflats.py +21 -24
  90. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scripts/control/selector.py +14 -9
  91. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scripts/imaging/autofocus.py +16 -22
  92. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scripts/imaging/imaging.py +60 -74
  93. pyobs_core-2.0.0.dev4/pyobs/robotic/scripts/utils/callmodule.py +86 -0
  94. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/lco/scripts/autofocus.py +30 -41
  95. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/lco/scripts/default.py +56 -69
  96. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/utils/exptime/stellarexptime.py +23 -16
  97. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/utils/skyflats/flatfielder.py +72 -60
  98. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/enums.py +36 -1
  99. pyobs_core-2.0.0.dev4/pyobs/utils/logging/context.py +24 -0
  100. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/parallel.py +15 -3
  101. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/shellcommand.py +10 -11
  102. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyproject.toml +9 -1
  103. pyobs_core-1.52.16/pyobs/comm/dbus/__init__.py +0 -3
  104. pyobs_core-1.52.16/pyobs/comm/dbus/dbuscomm.py +0 -591
  105. pyobs_core-1.52.16/pyobs/comm/dbus/patch.py +0 -112
  106. pyobs_core-1.52.16/pyobs/comm/local/localcomm.py +0 -98
  107. pyobs_core-1.52.16/pyobs/comm/mock/__init__.py +0 -3
  108. pyobs_core-1.52.16/pyobs/comm/mock/mockcomm.py +0 -85
  109. pyobs_core-1.52.16/pyobs/comm/xmpp/rpc.py +0 -274
  110. pyobs_core-1.52.16/pyobs/interfaces/IExposure.py +0 -33
  111. pyobs_core-1.52.16/pyobs/interfaces/IExposureTime.py +0 -43
  112. pyobs_core-1.52.16/pyobs/interfaces/ILatLon.py +0 -38
  113. pyobs_core-1.52.16/pyobs/interfaces/IMode.py +0 -62
  114. pyobs_core-1.52.16/pyobs/interfaces/IModule.py +0 -40
  115. pyobs_core-1.52.16/pyobs/interfaces/IMultiFiber.py +0 -73
  116. pyobs_core-1.52.16/pyobs/interfaces/IReady.py +0 -22
  117. pyobs_core-1.52.16/pyobs/interfaces/IRotation.py +0 -23
  118. pyobs_core-1.52.16/pyobs/interfaces/ITemperatures.py +0 -24
  119. pyobs_core-1.52.16/pyobs/interfaces/IVideo.py +0 -22
  120. pyobs_core-1.52.16/pyobs/modules/camera/dummycamera.py +0 -302
  121. pyobs_core-1.52.16/pyobs/modules/telescope/dummytelescope.py +0 -346
  122. pyobs_core-1.52.16/pyobs/robotic/scheduler/targets/__init__.py +0 -5
  123. pyobs_core-1.52.16/pyobs/robotic/scripts/utils/callmodule.py +0 -66
  124. pyobs_core-1.52.16/pyobs/utils/simulation/__init__.py +0 -11
  125. pyobs_core-1.52.16/pyobs/utils/simulation/camera.py +0 -284
  126. pyobs_core-1.52.16/pyobs/utils/simulation/telescope.py +0 -200
  127. pyobs_core-1.52.16/pyobs/utils/simulation/world.py +0 -107
  128. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/.gitignore +0 -0
  129. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/LICENSE +0 -0
  130. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/__init__.py +0 -0
  131. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/cli/__init__.py +0 -0
  132. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/cli/_cli.py +0 -0
  133. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/cli/pyobsd.py +0 -0
  134. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/cli/pyobsw.py +0 -0
  135. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/comm/__init__.py +0 -0
  136. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/comm/dummy/__init__.py +0 -0
  137. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/comm/dummy/dummycomm.py +0 -0
  138. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/comm/local/__init__.py +0 -0
  139. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/comm/local/localnetwork.py +0 -0
  140. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/comm/xmpp/__init__.py +0 -0
  141. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/comm/xmpp/xep_0009/__init__.py +0 -0
  142. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/comm/xmpp/xep_0009/binding.py +0 -0
  143. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/comm/xmpp/xep_0009/rpc.py +0 -0
  144. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/comm/xmpp/xep_0009_timeout/__init__.py +0 -0
  145. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/comm/xmpp/xep_0009_timeout/stanza/RPC.py +0 -0
  146. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/comm/xmpp/xep_0009_timeout/stanza/__init__.py +0 -0
  147. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/__init__.py +0 -0
  148. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/badweather.py +0 -0
  149. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/exposurestatuschanged.py +0 -0
  150. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/filterchanged.py +0 -0
  151. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/focusfound.py +0 -0
  152. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/goodweather.py +0 -0
  153. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/modechanged.py +0 -0
  154. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/moduleclosed.py +0 -0
  155. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/moduleopened.py +0 -0
  156. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/motionstatuschanged.py +0 -0
  157. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/move.py +0 -0
  158. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/newimage.py +0 -0
  159. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/newspectrum.py +0 -0
  160. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/offsets.py +0 -0
  161. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/roofclosing.py +0 -0
  162. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/roofopened.py +0 -0
  163. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/taskfailed.py +0 -0
  164. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/taskfinished.py +0 -0
  165. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/taskstarted.py +0 -0
  166. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/events/testevent.py +0 -0
  167. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/__init__.py +0 -0
  168. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/image.py +0 -0
  169. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/meta/__init__.py +0 -0
  170. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/meta/altazoffsets.py +0 -0
  171. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/meta/exptime.py +0 -0
  172. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/meta/genericoffset.py +0 -0
  173. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/meta/onskydistance.py +0 -0
  174. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/meta/pixeloffsets.py +0 -0
  175. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/meta/radecoffsets.py +0 -0
  176. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/meta/skyoffsets.py +0 -0
  177. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processor.py +0 -0
  178. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/__init__.py +0 -0
  179. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/_daobackgroundremover.py +0 -0
  180. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/annotation/__init__.py +0 -0
  181. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/annotation/_pillow.py +0 -0
  182. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/annotation/circle.py +0 -0
  183. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/annotation/crosshair.py +0 -0
  184. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/annotation/text.py +0 -0
  185. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/astrometry/__init__.py +0 -0
  186. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/astrometry/_dotnet_request.py +0 -0
  187. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/astrometry/_dotnet_request_builder.py +0 -0
  188. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/astrometry/_dotnet_request_logger.py +0 -0
  189. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/astrometry/_dotnet_response_saver.py +0 -0
  190. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/astrometry/astrometry.py +0 -0
  191. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/astrometry/dotnet.py +0 -0
  192. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/calibration/__init__.py +0 -0
  193. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/calibration/_calibration_cache.py +0 -0
  194. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/calibration/_ccddata_calibrator.py +0 -0
  195. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/calibration/calibration.py +0 -0
  196. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/detection/__init__.py +0 -0
  197. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/detection/_pysep_stats_calculator.py +0 -0
  198. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/detection/_source_catalog.py +0 -0
  199. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/detection/daophot.py +0 -0
  200. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/detection/pysep.py +0 -0
  201. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/detection/simpledisk.py +0 -0
  202. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/detection/sourcedetection.py +0 -0
  203. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/exptime/__init__.py +0 -0
  204. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/exptime/exptime.py +0 -0
  205. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/exptime/star.py +0 -0
  206. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/image/__init__.py +0 -0
  207. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/image/addfitsheaders.py +0 -0
  208. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/image/download.py +0 -0
  209. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/image/flip.py +0 -0
  210. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/image/grayscale.py +0 -0
  211. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/image/httpserver.py +0 -0
  212. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/image/normalize.py +0 -0
  213. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/image/save.py +0 -0
  214. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/image/saveimage.py +0 -0
  215. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/image/smooth.py +0 -0
  216. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/image/softbin.py +0 -0
  217. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/misc/__init__.py +0 -0
  218. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/misc/addmask.py +0 -0
  219. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/misc/catalogcircularmask.py +0 -0
  220. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/misc/circularmask.py +0 -0
  221. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/misc/createfilename.py +0 -0
  222. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/misc/imagesourcefilter.py +0 -0
  223. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/misc/removebackground.py +0 -0
  224. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/modules/__init__.py +0 -0
  225. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/offsets/__init__.py +0 -0
  226. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/offsets/astrometry.py +0 -0
  227. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/offsets/brighteststar.py +0 -0
  228. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/offsets/brighteststar_guiding.py +0 -0
  229. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/offsets/dummyoffsets.py +0 -0
  230. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/offsets/dummyskyoffsets.py +0 -0
  231. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/offsets/fitsheader.py +0 -0
  232. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/offsets/offsets.py +0 -0
  233. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/offsets/pixeloffsets.py +0 -0
  234. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/offsets/projected.py +0 -0
  235. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/photometry/__init__.py +0 -0
  236. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/photometry/_photometry_calculator.py +0 -0
  237. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/photometry/_photutil_aperture_photometry.py +0 -0
  238. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/photometry/_sep_aperture_photometry.py +0 -0
  239. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/photometry/aperture_photometry.py +0 -0
  240. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/photometry/photometry.py +0 -0
  241. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/photometry/photutil.py +0 -0
  242. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/photometry/pysep.py +0 -0
  243. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/wcs/__init__.py +0 -0
  244. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/images/processors/wcs/solarhelioprojective.py +0 -0
  245. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IAbortable.py +0 -0
  246. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IAcquisition.py +0 -0
  247. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IAutoGuiding.py +0 -0
  248. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IAutonomous.py +0 -0
  249. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/ICalibrate.py +0 -0
  250. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/ICamera.py +0 -0
  251. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IData.py +0 -0
  252. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IDome.py +0 -0
  253. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IFitsHeaderAfter.py +0 -0
  254. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IFitsHeaderBefore.py +0 -0
  255. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IFlatField.py +0 -0
  256. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IFocusModel.py +0 -0
  257. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IPointingSeries.py +0 -0
  258. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IRoof.py +0 -0
  259. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IRunnable.py +0 -0
  260. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IScriptRunner.py +0 -0
  261. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/ISpectrograph.py +0 -0
  262. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IStartStop.py +0 -0
  263. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/ISyncTarget.py +0 -0
  264. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/ITelescope.py +0 -0
  265. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/interfaces/IWeather.py +0 -0
  266. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/mixins/__init__.py +0 -0
  267. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/mixins/fitsnamespace.py +0 -0
  268. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/mixins/pipeline.py +0 -0
  269. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/__init__.py +0 -0
  270. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/camera/__init__.py +0 -0
  271. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/camera/adaptive.py +0 -0
  272. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/flatfield/__init__.py +0 -0
  273. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/focus/__init__.py +0 -0
  274. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/image/__init__.py +0 -0
  275. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/image/imagewatcher.py +0 -0
  276. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/image/imagewriter.py +0 -0
  277. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/image/pipeline.py +0 -0
  278. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/image/seeing.py +0 -0
  279. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/pointing/__init__.py +0 -0
  280. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/pointing/dummyacquisition.py +0 -0
  281. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/pointing/dummyguiding.py +0 -0
  282. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/pointing/guidingstatistics/__init__.py +0 -0
  283. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/pointing/guidingstatistics/guidingstatistics.py +0 -0
  284. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/pointing/guidingstatistics/pixeloffset.py +0 -0
  285. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/pointing/guidingstatistics/skyoffsets.py +0 -0
  286. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/pointing/guidingstatistics/uptime.py +0 -0
  287. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/robotic/__init__.py +0 -0
  288. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/robotic/mastermind.py +0 -0
  289. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/robotic/scheduler.py +0 -0
  290. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/robotic/scriptrunner.py +0 -0
  291. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/roof/__init__.py +0 -0
  292. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/roof/basedome.py +0 -0
  293. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/telescope/__init__.py +0 -0
  294. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/test/__init__.py +0 -0
  295. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/test/standalone.py +0 -0
  296. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/utils/__init__.py +0 -0
  297. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/utils/fluentlogger.py +0 -0
  298. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/utils/httpfilecache.py +0 -0
  299. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/utils/matrix.py +0 -0
  300. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/weather/__init__.py +0 -0
  301. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/weather/weather.py +0 -0
  302. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/modules/weather/weather_state.py +0 -0
  303. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/py.typed +0 -0
  304. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/__init__.py +0 -0
  305. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/observation.py +0 -0
  306. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/__init__.py +0 -0
  307. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/astroplanscheduler.py +0 -0
  308. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/constraints/__init__.py +0 -0
  309. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/constraints/airmassconstraint.py +0 -0
  310. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/constraints/constraint.py +0 -0
  311. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/constraints/moonilluminationconstraint.py +0 -0
  312. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/constraints/moonseparationconstraint.py +0 -0
  313. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/constraints/solarelevationconstraint.py +0 -0
  314. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/constraints/timeconstraint.py +0 -0
  315. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/dataprovider.py +0 -0
  316. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/merits/__init__.py +0 -0
  317. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/merits/aftertime.py +0 -0
  318. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/merits/beforetime.py +0 -0
  319. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/merits/constant.py +0 -0
  320. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/merits/follow.py +0 -0
  321. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/merits/interval.py +0 -0
  322. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/merits/merit.py +0 -0
  323. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/merits/pernight.py +0 -0
  324. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/merits/random.py +0 -0
  325. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/merits/timewindow.py +0 -0
  326. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/observationarchiveevolution.py +0 -0
  327. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/ondemandscheduler.py +0 -0
  328. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/targets/dynamictarget.py +0 -0
  329. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/targets/picker/__init__.py +0 -0
  330. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/targets/picker/csvpicker.py +0 -0
  331. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/targets/picker/picker.py +0 -0
  332. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/targets/siderealtarget.py +0 -0
  333. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/targets/target.py +0 -0
  334. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scheduler/taskscheduler.py +0 -0
  335. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scripts/__init__.py +0 -0
  336. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scripts/calibration/__init__.py +0 -0
  337. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scripts/control/__init__.py +0 -0
  338. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scripts/control/cases.py +0 -0
  339. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scripts/control/conditional.py +0 -0
  340. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scripts/control/parallel.py +0 -0
  341. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scripts/control/sequential.py +0 -0
  342. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scripts/imaging/__init__.py +0 -0
  343. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scripts/imaging/transitimaging.py +0 -0
  344. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scripts/script.py +0 -0
  345. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scripts/utils/__init__.py +0 -0
  346. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scripts/utils/debugtrigger.py +0 -0
  347. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/scripts/utils/log.py +0 -0
  348. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/__init__.py +0 -0
  349. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/backend/__init__.py +0 -0
  350. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/backend/observationarchive.py +0 -0
  351. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/backend/taskarchive.py +0 -0
  352. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/filesystem/__init__.py +0 -0
  353. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/filesystem/observationarchive.py +0 -0
  354. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/filesystem/taskarchive.py +0 -0
  355. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/lco/__init__.py +0 -0
  356. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/lco/_portal.py +0 -0
  357. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/lco/_schedulereader.py +0 -0
  358. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/lco/_schedulewriter.py +0 -0
  359. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/lco/configdb.py +0 -0
  360. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/lco/mockobservationarchive.py +0 -0
  361. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/lco/observationarchive.py +0 -0
  362. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/lco/scripts/__init__.py +0 -0
  363. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/lco/scripts/script.py +0 -0
  364. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/lco/task.py +0 -0
  365. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/lco/taskarchive.py +0 -0
  366. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/lco/taskrunner.py +0 -0
  367. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/memory/__init__.py +0 -0
  368. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/memory/observationarchive.py +0 -0
  369. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/memory/taskarchive.py +0 -0
  370. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/observationarchive.py +0 -0
  371. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/storage/taskarchive.py +0 -0
  372. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/task.py +0 -0
  373. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/taskrunner.py +0 -0
  374. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/utils/__init__.py +0 -0
  375. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/utils/archive/__init__.py +0 -0
  376. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/utils/archive/archive.py +0 -0
  377. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/utils/archive/local_archive.py +0 -0
  378. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/utils/archive/pyobs_archive.py +0 -0
  379. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/utils/exptime/__init__.py +0 -0
  380. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/utils/exptime/exptime.py +0 -0
  381. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/utils/skyflats/__init__.py +0 -0
  382. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/utils/skyflats/exptimeeval.py +0 -0
  383. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/utils/skyflats/pointing/__init__.py +0 -0
  384. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/utils/skyflats/pointing/base.py +0 -0
  385. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/utils/skyflats/pointing/static.py +0 -0
  386. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/utils/skyflats/priorities/__init__.py +0 -0
  387. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/utils/skyflats/priorities/archive.py +0 -0
  388. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/utils/skyflats/priorities/base.py +0 -0
  389. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/utils/skyflats/priorities/const.py +0 -0
  390. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/robotic/utils/skyflats/scheduler.py +0 -0
  391. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/__init__.py +0 -0
  392. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/average.py +0 -0
  393. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/cache.py +0 -0
  394. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/config.py +0 -0
  395. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/coordinates.py +0 -0
  396. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/curvefit.py +0 -0
  397. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/exceptions.py +0 -0
  398. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/fits.py +0 -0
  399. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/focusseries/__init__.py +0 -0
  400. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/focusseries/base.py +0 -0
  401. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/focusseries/photometry.py +0 -0
  402. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/focusseries/projection.py +0 -0
  403. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/grids/__init__.py +0 -0
  404. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/grids/filters.py +0 -0
  405. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/grids/grid.py +0 -0
  406. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/grids/gridnode.py +0 -0
  407. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/grids/pipeline.py +0 -0
  408. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/gui/__init__.py +0 -0
  409. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/gui/camera/__init__.py +0 -0
  410. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/gui/camera/binningwidget.py +0 -0
  411. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/gui/camera/datadisplaywidget.py +0 -0
  412. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/gui/camera/exposewidget.py +0 -0
  413. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/gui/camera/exposuretimewidget.py +0 -0
  414. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/gui/camera/imageformatwidget.py +0 -0
  415. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/gui/camera/listpickerdialog.py +0 -0
  416. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/gui/camera/windowingwidget.py +0 -0
  417. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/http.py +0 -0
  418. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/influxdb.py +0 -0
  419. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/logger.py +0 -0
  420. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/logging/__init__.py +0 -0
  421. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/logging/resolvableerror.py +0 -0
  422. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/modulegui.py +0 -0
  423. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/offsets/__init__.py +0 -0
  424. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/offsets/applyaltazoffsets.py +0 -0
  425. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/offsets/applyoffsets.py +0 -0
  426. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/offsets/applyradecoffsets.py +0 -0
  427. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/pipeline/__init__.py +0 -0
  428. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/pipeline/night.py +0 -0
  429. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/pipeline/pipeline.py +0 -0
  430. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/publisher/__init__.py +0 -0
  431. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/publisher/csv.py +0 -0
  432. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/publisher/http.py +0 -0
  433. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/publisher/log.py +0 -0
  434. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/publisher/multi.py +0 -0
  435. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/publisher/publisher.py +0 -0
  436. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/serialization.py +0 -0
  437. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/threads/__init__.py +0 -0
  438. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/threads/lockwithabort.py +0 -0
  439. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/time.py +0 -0
  440. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/utils/types.py +0 -0
  441. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/version.py +0 -0
  442. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/vfs/__init__.py +0 -0
  443. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/vfs/archivefile.py +0 -0
  444. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/vfs/bufferedfile.py +0 -0
  445. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/vfs/file.py +0 -0
  446. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/vfs/filelists/__init__.py +0 -0
  447. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/vfs/filelists/filelist.py +0 -0
  448. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/vfs/filelists/testing.py +0 -0
  449. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/vfs/httpfile.py +0 -0
  450. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/vfs/localfile.py +0 -0
  451. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/vfs/memfile.py +0 -0
  452. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/vfs/sftpfile.py +0 -0
  453. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/vfs/smbfile.py +0 -0
  454. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/vfs/sshfile.py +0 -0
  455. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/vfs/tempfile.py +0 -0
  456. {pyobs_core-1.52.16 → pyobs_core-2.0.0.dev4}/pyobs/vfs/vfs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyobs-core
3
- Version: 1.52.16
3
+ Version: 2.0.0.dev4
4
4
  Summary: robotic telescope software
5
5
  Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>
6
6
  License-Expression: MIT
@@ -15,6 +15,7 @@ Requires-Dist: colour<0.2,>=0.1.5
15
15
  Requires-Dist: dacite>=1.9.2
16
16
  Requires-Dist: dbus-next<0.3,>=0.2.3
17
17
  Requires-Dist: influxdb-client>=1.49.0
18
+ Requires-Dist: logging-journald>=0.6.11
18
19
  Requires-Dist: numpy<3,>=2.2.5
19
20
  Requires-Dist: pandas>=2.2.3
20
21
  Requires-Dist: paramiko>=3.5.1
@@ -13,6 +13,7 @@ import yaml
13
13
  from pyobs.modules import Module
14
14
  from pyobs.object import get_class_from_string, get_object
15
15
  from pyobs.utils.config import pre_process_yaml
16
+ from pyobs.utils.logging.context import ModuleNameFilter
16
17
 
17
18
  # just init logger with something here, will be overwritten in __init__
18
19
  log = logging.getLogger(__name__)
@@ -37,6 +38,7 @@ class Application:
37
38
  config: str,
38
39
  log_file: str | None = None,
39
40
  log_level: str = "info",
41
+ syslog: bool = False,
40
42
  influx_log: InfluxLogConfig | None = None,
41
43
  **kwargs: Any,
42
44
  ):
@@ -46,6 +48,8 @@ class Application:
46
48
  config: Name of config file.
47
49
  log_file: Name of log file, if any.
48
50
  log_level: Logging level.
51
+ syslog: Send log to systemd journal, tagged with SYSLOG_IDENTIFIER=pyobs
52
+ and PYOBS_MODULE=<module name>. Requires logging-journald.
49
53
  influx_log: Log to influx DB.
50
54
  """
51
55
 
@@ -53,13 +57,22 @@ class Application:
53
57
  self._config = config
54
58
  config_base = os.path.splitext(os.path.basename(config))[0]
55
59
 
56
- # formatter for logging, and list of logging handlers
57
- formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(filename)s:%(lineno)d %(message)s")
60
+ # filter that injects %(pyobs_module)s into every LogRecord from the context var
61
+ module_name_filter = ModuleNameFilter()
62
+
63
+ # formatters — file/stream include the module name as text; journal omits
64
+ # timestamp/priority since those are captured natively by journald
65
+ formatter = logging.Formatter(
66
+ "%(asctime)s [%(levelname)s] (%(pyobs_module)s) %(filename)s:%(lineno)d %(message)s"
67
+ )
68
+ journal_formatter = logging.Formatter("%(pyobs_module)s %(filename)s:%(lineno)d %(message)s")
69
+
58
70
  handlers: list[logging.Handler] = []
59
71
 
60
72
  # create stdout logging handler
61
73
  stream_handler = logging.StreamHandler()
62
74
  stream_handler.setFormatter(formatter)
75
+ stream_handler.addFilter(module_name_filter)
63
76
  handlers.append(stream_handler)
64
77
 
65
78
  # create file logging handler, if log file is given
@@ -70,10 +83,30 @@ class Application:
70
83
  else:
71
84
  file_handler = logging.handlers.WatchedFileHandler(log_file)
72
85
 
73
- # add log file handler
74
86
  file_handler.setFormatter(formatter)
87
+ file_handler.addFilter(module_name_filter)
75
88
  handlers.append(file_handler)
76
89
 
90
+ # systemd journal handler?
91
+ if syslog:
92
+ from logging_journald import JournaldLogHandler # type: ignore[import-untyped]
93
+
94
+ class PyobsJournaldLogHandler(JournaldLogHandler): # type: ignore[misc]
95
+ """JournaldLogHandler that adds SYSLOG_IDENTIFIER=pyobs and PYOBS_MODULE per record."""
96
+
97
+ def __init__(self, **kw: Any) -> None:
98
+ super().__init__(identifier="pyobs", **kw)
99
+
100
+ def _format_record(self, record: logging.LogRecord) -> list[tuple[str, Any]]:
101
+ pairs: list[tuple[str, Any]] = super()._format_record(record)
102
+ pairs.append(("PYOBS_MODULE", getattr(record, "pyobs_module", "")))
103
+ return pairs
104
+
105
+ journal_handler = PyobsJournaldLogHandler()
106
+ journal_handler.setFormatter(journal_formatter)
107
+ journal_handler.addFilter(module_name_filter)
108
+ handlers.append(journal_handler)
109
+
77
110
  # influx handler?
78
111
  if influx_log is not None:
79
112
  from pyobs.utils.influxdb import InfluxHandler
@@ -91,7 +124,12 @@ class Application:
91
124
 
92
125
  # set pyobs logger
93
126
  global log
127
+ from pathlib import Path
128
+
129
+ from pyobs.utils.logging.context import module_name
130
+
94
131
  log = logging.getLogger(__name__)
132
+ module_name.set(Path(self._config).stem)
95
133
 
96
134
  # load config
97
135
  log.info("Loading configuration from %s...", self._config)
@@ -102,8 +140,17 @@ class Application:
102
140
  class_name = cfg["class"]
103
141
  klass = get_class_from_string(class_name)
104
142
 
105
- # create event loop
106
- self._loop = klass.new_event_loop()
143
+ # create event loop — if top-level class doesn't override new_event_loop,
144
+ # check child modules for one that does (e.g. pyobs_gui.GUI in a MultiModule)
145
+ loop_class = klass
146
+ if klass.new_event_loop is Module.new_event_loop:
147
+ for mod_cfg in cfg.get("modules", {}).values():
148
+ if isinstance(mod_cfg, dict) and "class" in mod_cfg:
149
+ child_klass = get_class_from_string(mod_cfg["class"])
150
+ if child_klass.new_event_loop is not Module.new_event_loop:
151
+ loop_class = child_klass
152
+ break
153
+ self._loop = loop_class.new_event_loop()
107
154
  asyncio.set_event_loop(self._loop)
108
155
 
109
156
  # create module and open it
@@ -137,9 +184,6 @@ class Application:
137
184
  def _signal_handler(self, sig: int) -> None:
138
185
  """React to signals and quit the module."""
139
186
 
140
- # stop loop
141
- # loop = asyncio.get_running_loop()
142
- # loop.stop()
143
187
  self._module.quit()
144
188
 
145
189
  # reset signal handlers
@@ -27,6 +27,13 @@ class BackgroundTask:
27
27
  self._task = asyncio.create_task(self._func_wrapper())
28
28
 
29
29
  async def _func_wrapper(self) -> None:
30
+ # stamp the module name into the context var so all logging within this task
31
+ # (and any tasks it spawns) carries the correct PYOBS_MODULE field
32
+ from pyobs.utils.logging.context import module_name as _module_name_var
33
+
34
+ if hasattr(self._parent, "name"):
35
+ _module_name_var.set(self._parent.name)
36
+
30
37
  start = time.time()
31
38
  finish_count = 0
32
39
 
@@ -41,6 +41,12 @@ class PyobsCLI(CLI):
41
41
  self._parser.add_argument(
42
42
  "-l", "--log-file", type=str, help="file to write log into", default=self._config.get("log_file", None)
43
43
  )
44
+ self._parser.add_argument(
45
+ "--syslog",
46
+ action="store_true",
47
+ help="send log to systemd journal",
48
+ default=self._config.get("syslog", False),
49
+ )
44
50
  self._parser.add_argument(
45
51
  "--influx-log",
46
52
  type=str,
@@ -1,25 +1,28 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import functools
4
5
  import inspect
5
6
  import logging
7
+ import sys
6
8
  from collections.abc import Callable, Coroutine
7
- from typing import TYPE_CHECKING, Any, TypeVar, overload
9
+ from typing import TYPE_CHECKING, Any, overload
8
10
 
9
11
  import pyobs.interfaces
10
12
  from pyobs.events import Event, LogEvent, ModuleClosedEvent
11
13
  from pyobs.interfaces import Interface
14
+ from pyobs.utils.enums import ModuleState
12
15
 
13
16
  from .commlogging import CommLoggingHandler
14
- from .proxy import Proxy
17
+ from .proxy import Proxy, ProxyType, _ProxyContext
15
18
 
16
19
  if TYPE_CHECKING:
17
20
  from pyobs.modules import Module
18
21
 
19
- log = logging.getLogger(__name__)
20
-
22
+ StateCallback = Callable[[Any], None]
23
+ PresenceCallback = Callable[["ModuleState", str], None]
21
24
 
22
- ProxyType = TypeVar("ProxyType")
25
+ log = logging.getLogger(__name__)
23
26
 
24
27
 
25
28
  class Comm:
@@ -27,13 +30,13 @@ class Comm:
27
30
 
28
31
  __module__ = "pyobs.comm"
29
32
 
30
- def __init__(self, cache_proxies: bool = True):
33
+ def __init__(self) -> None:
31
34
  """Creates a comm module."""
32
35
 
33
36
  self._proxies: dict[str, Proxy] = {}
37
+ self._state_subscriptions: dict[str, list[tuple[type[Interface], StateCallback]]] = {}
34
38
  self._module: Module | None = None
35
39
  self._log_queue: asyncio.Queue[LogEvent] = asyncio.Queue()
36
- self._cache_proxies = cache_proxies
37
40
  self._logging_task: asyncio.Task[Any] | None = None
38
41
  self._event_handlers: dict[type[Event], list[Callable[[Event, str], Coroutine[Any, Any, bool]]]] = {}
39
42
  self._closing = asyncio.Event()
@@ -64,9 +67,14 @@ class Comm:
64
67
  """Open module."""
65
68
 
66
69
  # add handler to global logger
67
- handler = CommLoggingHandler(self)
68
- handler.setLevel(logging.INFO)
69
- logging.getLogger().addHandler(handler)
70
+ root_logger = logging.getLogger()
71
+ if not any(isinstance(h, CommLoggingHandler) for h in root_logger.handlers):
72
+ from pyobs.utils.logging.context import ModuleNameFilter
73
+
74
+ handler = CommLoggingHandler(self)
75
+ handler.setLevel(logging.INFO)
76
+ handler.addFilter(ModuleNameFilter())
77
+ root_logger.addHandler(handler)
70
78
 
71
79
  # start logging thread
72
80
  self._logging_task = asyncio.create_task(self._logging())
@@ -117,7 +125,7 @@ class Comm:
117
125
  return None
118
126
 
119
127
  # if client doesn't exist or we disabled caching, fetch a new proxy
120
- if client not in self._proxies or not self._cache_proxies:
128
+ if client not in self._proxies:
121
129
  # get interfaces
122
130
  try:
123
131
  interfaces = await self.get_interfaces(client)
@@ -126,18 +134,20 @@ class Comm:
126
134
 
127
135
  # create new proxy
128
136
  proxy = Proxy(self, client, interfaces)
137
+
138
+ # subscribe to state
139
+ for interface in interfaces:
140
+ if getattr(interface, "State", None) is not None:
141
+ await self.subscribe_state(client, interface, functools.partial(proxy.update_state, interface))
142
+
129
143
  self._proxies[client] = proxy
130
144
 
131
145
  # return proxy
132
146
  return self._proxies[client]
133
147
 
134
- @overload
135
- async def proxy(self, name_or_object: str | object, obj_type: type[ProxyType]) -> ProxyType: ...
136
-
137
- @overload
138
- async def proxy(self, name_or_object: str | object, obj_type: type[ProxyType] | None = None) -> Any: ...
139
-
140
- async def proxy(self, name_or_object: str | object, obj_type: type[ProxyType] | None = None) -> Any | ProxyType:
148
+ async def _resolve_proxy(
149
+ self, name_or_object: str | object, obj_type: type[ProxyType] | None = None
150
+ ) -> Any | ProxyType:
141
151
  """Returns object directly if it is of given type. Otherwise get proxy of client with given name and check type.
142
152
 
143
153
  If name_or_object is an object:
@@ -184,16 +194,41 @@ class Comm:
184
194
  # completely wrong...
185
195
  raise ValueError(f'Given parameter is neither a name nor an object of requested type "{obj_type}".')
186
196
 
187
- async def safe_proxy(
197
+ async def _safe_resolve_proxy(
188
198
  self, name_or_object: str | object, obj_type: type[ProxyType] | None = None
189
199
  ) -> Any | ProxyType | None:
190
200
  """Calls proxy() in a safe way and returns None instead of raising an exception."""
191
201
 
192
202
  try:
193
- return await self.proxy(name_or_object, obj_type)
203
+ return await self._resolve_proxy(name_or_object, obj_type)
194
204
  except ValueError:
195
205
  return None
196
206
 
207
+ @overload
208
+ def proxy(self, name_or_object: str | object, obj_type: type[ProxyType]) -> _ProxyContext[ProxyType]: ...
209
+ @overload
210
+ def proxy(self, name_or_object: str | object, obj_type: None = None) -> _ProxyContext[Any]: ...
211
+
212
+ def proxy(self, name_or_object: str | object, obj_type: type[ProxyType] | None = None) -> _ProxyContext[Any]:
213
+ """Returns a context manager; use as `async with self.proxy(...) as x:`."""
214
+ return _ProxyContext(self._resolve_proxy(name_or_object, obj_type))
215
+
216
+ @overload
217
+ def safe_proxy(
218
+ self, name_or_object: str | object, obj_type: type[ProxyType]
219
+ ) -> _ProxyContext[ProxyType | None]: ...
220
+ @overload
221
+ def safe_proxy(self, name_or_object: str | object, obj_type: None = None) -> _ProxyContext[Any]: ...
222
+
223
+ def safe_proxy(self, name_or_object: str | object, obj_type: type[ProxyType] | None = None) -> _ProxyContext[Any]:
224
+ """Same as proxy(), but yields None inside the block instead of raising."""
225
+ return _ProxyContext(self._safe_resolve_proxy(name_or_object, obj_type))
226
+
227
+ async def has_proxy(self, name_or_object: str | object, obj_type: type[Any] | None = None) -> bool:
228
+ """True if a proxy of the given type can currently be resolved. Doesn't keep a reference
229
+ to it, so doesn't need async with the way proxy()/safe_proxy() do."""
230
+ return await self._safe_resolve_proxy(name_or_object, obj_type) is not None
231
+
197
232
  async def _client_disconnected(self, event: Event, sender: str) -> bool:
198
233
  """Called when a client disconnects.
199
234
 
@@ -203,9 +238,15 @@ class Comm:
203
238
 
204
239
  """
205
240
 
206
- # if a client disconnects, we remove its proxy
241
+ # if a client disconnects, clear its proxy state then evict it
207
242
  if sender in self._proxies:
243
+ self._proxies[sender].clear_state()
208
244
  del self._proxies[sender]
245
+
246
+ # tear down any state subscriptions held for that client
247
+ for interface, callback in self._state_subscriptions.pop(sender, []):
248
+ await self.unsubscribe_state(sender, interface, callback)
249
+
209
250
  return True
210
251
 
211
252
  @property
@@ -397,6 +438,143 @@ class Comm:
397
438
  ) -> None:
398
439
  pass
399
440
 
441
+ async def set_state(self, interface: type[Interface], state: Any) -> None:
442
+ """Publish state for this module.
443
+
444
+ Args:
445
+ interface: Interface type for the state.
446
+ state: State object to publish.
447
+ """
448
+ await self._set_state(interface, state)
449
+
450
+ async def _set_state(self, interface: type[Interface], state: Any) -> None:
451
+ pass
452
+
453
+ @staticmethod
454
+ def _interface_from_capabilities(caps_cls: type) -> type:
455
+ outer_name = caps_cls.__qualname__.rsplit(".", 1)[0]
456
+ return getattr(sys.modules[caps_cls.__module__], outer_name)
457
+
458
+ async def set_capabilities(self, capabilities: Any) -> None:
459
+ """Publish capabilities for this module.
460
+
461
+ Called by Module.open() for each interface that defines a Capabilities
462
+ dataclass. Not intended to be called directly by module authors after
463
+ that point — capabilities are fixed for the module lifetime.
464
+
465
+ Args:
466
+ capabilities: Capabilities dataclass instance.
467
+ """
468
+ interface = Comm._interface_from_capabilities(type(capabilities))
469
+ await self._set_capabilities(interface, capabilities)
470
+
471
+ async def _set_capabilities(self, interface: type[Interface], capabilities: Any) -> None:
472
+ pass
473
+
474
+ async def get_capabilities(self, module: str, interface: type[Interface]) -> Any | None:
475
+ """Fetch and deserialize capabilities for a remote module's interface.
476
+
477
+ Args:
478
+ module: Module name (e.g. "camera").
479
+ interface: Interface class whose Capabilities dataclass to fetch.
480
+
481
+ Returns:
482
+ Deserialized Capabilities dataclass instance, or None if not published.
483
+ """
484
+ return await self._get_capabilities(module, interface)
485
+
486
+ async def _get_capabilities(self, module: str, interface: type[Interface]) -> Any | None:
487
+ return None
488
+
489
+ async def set_presence(self, state: ModuleState, error_string: str = "") -> None:
490
+ """Publish presence for this module (module lifecycle state).
491
+
492
+ Called automatically by Module.set_state — not intended to be called
493
+ directly by module authors.
494
+
495
+ Args:
496
+ state: Current module lifecycle state.
497
+ error_string: Error message, used when state is ERROR.
498
+ """
499
+ await self._set_presence(state, error_string)
500
+
501
+ async def _set_presence(self, state: ModuleState, error_string: str = "") -> None:
502
+ pass
503
+
504
+ def get_client_state(self, module: str) -> tuple[ModuleState, str] | None:
505
+ """Return the last known presence state of a connected module.
506
+
507
+ Returns (ModuleState, error_string) or None if the module is not connected.
508
+ This replaces the old get_state()/get_error_string() RPC pattern.
509
+ """
510
+ return self._get_client_state(module)
511
+
512
+ def _get_client_state(self, module: str) -> tuple[ModuleState, str] | None:
513
+ return None
514
+
515
+ async def subscribe_state(
516
+ self,
517
+ module: str,
518
+ interface: type[Interface],
519
+ callback: StateCallback,
520
+ ) -> None:
521
+ """Subscribe to state updates for a given module and interface.
522
+
523
+ Delivers the current value immediately on subscribe.
524
+
525
+ Args:
526
+ module: Name of remote module.
527
+ interface: Interface type to subscribe to.
528
+ callback: Called with state object on each update.
529
+ """
530
+ self._state_subscriptions.setdefault(module, []).append((interface, callback))
531
+ await self._subscribe_state(module, interface, callback)
532
+
533
+ async def _subscribe_state(
534
+ self,
535
+ module: str,
536
+ interface: type[Interface],
537
+ callback: StateCallback,
538
+ ) -> None:
539
+ pass
540
+
541
+ async def unsubscribe_state(
542
+ self,
543
+ module: str,
544
+ interface: type[Interface],
545
+ callback: StateCallback,
546
+ ) -> None:
547
+ """Unsubscribe from state updates.
548
+
549
+ Args:
550
+ module: Name of remote module.
551
+ interface: Interface type to unsubscribe from.
552
+ callback: Callback that was registered.
553
+ """
554
+ await self._unsubscribe_state(module, interface, callback)
555
+
556
+ async def _unsubscribe_state(
557
+ self,
558
+ module: str,
559
+ interface: type[Interface],
560
+ callback: StateCallback,
561
+ ) -> None:
562
+ pass
563
+
564
+ async def subscribe_presence(self, module: str, callback: PresenceCallback) -> None:
565
+ """Subscribe to presence updates for a given module.
566
+
567
+ Delivers the current value immediately, then on every change.
568
+
569
+ Args:
570
+ module: Name of remote module.
571
+ callback: Called with (ModuleState, error_string) on each update.
572
+ """
573
+ await self._subscribe_presence(module, callback)
574
+
575
+ async def _subscribe_presence(self, module: str, callback: PresenceCallback) -> None:
576
+ pass
577
+
400
578
  def _send_event_to_module(self, event: Event, from_client: str) -> None:
401
579
  """Send an event to all connected modules.
402
580
 
@@ -34,9 +34,12 @@ class CommLoggingHandler(logging.Handler):
34
34
  # format message
35
35
  msg = self._formatter.format(rec) # noqa: UP031
36
36
 
37
- # create and send event
37
+ # create and send event — use pyobs_module if set (MultiModule context)
38
38
  time = datetime.fromtimestamp(rec.created, tz=UTC).strftime("%Y-%m-%dT%H:%M:%S.%f")
39
- entry = LogEvent(time, rec.levelname, os.path.basename(rec.pathname), rec.funcName, rec.lineno, msg)
39
+ sender = getattr(rec, "pyobs_module", "") or ""
40
+ entry = LogEvent(
41
+ time, rec.levelname, os.path.basename(rec.pathname), rec.funcName, rec.lineno, msg, sender=sender
42
+ )
40
43
  self._comm.log_message(entry)
41
44
 
42
45
 
@@ -0,0 +1,157 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable, Coroutine
4
+ from typing import Any
5
+
6
+ from pyobs.comm import Comm
7
+ from pyobs.comm.comm import PresenceCallback
8
+ from pyobs.events import Event
9
+ from pyobs.interfaces import Interface
10
+ from pyobs.utils.enums import ModuleState
11
+ from pyobs.utils.types import cast_response_to_real
12
+
13
+ from .localnetwork import LocalNetwork
14
+
15
+
16
+ class LocalComm(Comm):
17
+ def __init__(self, name: str, *args: Any, **kwargs: Any):
18
+ Comm.__init__(self, *args, **kwargs)
19
+
20
+ self._name = name
21
+ self._network = LocalNetwork()
22
+ self._network.connect_client(self)
23
+
24
+ # in-memory state and capabilities storage
25
+ self._states: dict[str, Any] = {} # node -> state object
26
+ self._state_handlers: dict[str, list[tuple[type[Interface], Callable[[Any], None]]]] = {}
27
+ self._capabilities: dict[type[Interface], Any] = {} # interface -> Capabilities object
28
+ self._presence: tuple[ModuleState, str] = (ModuleState.READY, "")
29
+ self._presence_callbacks: dict[str, list[PresenceCallback]] = {}
30
+
31
+ @property
32
+ def name(self) -> str:
33
+ """Name of this client."""
34
+ return self._name
35
+
36
+ @property
37
+ def clients(self) -> list[str]:
38
+ """Returns list of currently connected clients."""
39
+ return self._network.get_client_names()
40
+
41
+ async def get_interfaces(self, client: str) -> list[type[Interface]]:
42
+ """Returns list of interfaces for given client."""
43
+ remote_client = self._network.get_client(client)
44
+ return [] if not remote_client.has_module else remote_client.module.interfaces
45
+
46
+ async def _supports_interface(self, client: str, interface: type[Interface]) -> bool:
47
+ """Checks whether the given client supports the given interface."""
48
+ interfaces = await self.get_interfaces(client)
49
+ return interface in interfaces
50
+
51
+ async def execute(self, client: str, method: str, annotation: dict[str, Any], *args: Any) -> Any:
52
+ """Execute a given method on a remote client."""
53
+ remote_client = self._network.get_client(client)
54
+ if remote_client.module is None:
55
+ raise ValueError
56
+ simple_results = await remote_client.module.execute(method, *args, sender=self.name)
57
+ real_results = cast_response_to_real(
58
+ simple_results, annotation["return"], self.cast_to_real_pre, self.cast_to_real_post
59
+ )
60
+ return real_results
61
+
62
+ async def send_event(self, event: Event) -> None:
63
+ """Send an event to other clients."""
64
+ from pyobs.events import LogEvent as _LogEvent
65
+
66
+ sender = self.name
67
+ if isinstance(event, _LogEvent) and event.sender:
68
+ sender = event.sender
69
+ remote_clients = self._network.get_clients()
70
+ for client in remote_clients:
71
+ client._send_event_to_module(event, sender)
72
+
73
+ async def _register_events(
74
+ self, events: list[type[Event]], handler: Callable[[Event, str], Coroutine[Any, Any, bool]] | None = None
75
+ ) -> None:
76
+ pass
77
+
78
+ # -------------------------------------------------------------------------
79
+ # State
80
+ # -------------------------------------------------------------------------
81
+
82
+ async def _set_state(self, interface: type[Interface], state: Any) -> None:
83
+ """Publish state locally and dispatch to subscribers."""
84
+ node = f"pyobs:state:{self._name}:{interface.__name__}:{interface.version}"
85
+ self._states[node] = state
86
+
87
+ # dispatch to all subscribers for this node
88
+ if node in self._state_handlers:
89
+ for _, callback in self._state_handlers[node]:
90
+ callback(state)
91
+
92
+ async def _subscribe_state(self, module: str, interface: type[Interface], callback: Callable[[Any], None]) -> None:
93
+ """Subscribe to state updates from a remote module."""
94
+ node = f"pyobs:state:{module}:{interface.__name__}:{interface.version}"
95
+
96
+ if node not in self._state_handlers:
97
+ self._state_handlers[node] = []
98
+ self._state_handlers[node].append((interface, callback))
99
+
100
+ # also register on the remote comm so it can dispatch to us
101
+ try:
102
+ remote = self._network.get_client(module)
103
+ if node not in remote._state_handlers:
104
+ remote._state_handlers[node] = []
105
+ remote._state_handlers[node].append((interface, callback))
106
+
107
+ # deliver current value immediately if available
108
+ if node in remote._states:
109
+ callback(remote._states[node])
110
+ except KeyError:
111
+ pass # remote not connected yet
112
+
113
+ # -------------------------------------------------------------------------
114
+ # Capabilities
115
+ # -------------------------------------------------------------------------
116
+
117
+ async def _set_capabilities(self, interface: type[Interface], capabilities: Any) -> None:
118
+ """Store capabilities locally."""
119
+ self._capabilities[interface] = capabilities
120
+
121
+ async def _get_capabilities(self, module: str, interface: type[Interface]) -> Any | None:
122
+ """Fetch capabilities from a remote module."""
123
+ if not hasattr(interface, "Capabilities"):
124
+ return None
125
+ try:
126
+ remote = self._network.get_client(module)
127
+ return remote._capabilities.get(interface)
128
+ except KeyError:
129
+ return None
130
+
131
+ # -------------------------------------------------------------------------
132
+ # Presence
133
+ # -------------------------------------------------------------------------
134
+
135
+ async def _set_presence(self, state: ModuleState, error_string: str = "") -> None:
136
+ """Store presence state and dispatch to all subscribers."""
137
+ self._presence = (state, error_string)
138
+ for client in self._network.get_clients():
139
+ for cb in client._presence_callbacks.get(self._name, []):
140
+ cb(state, error_string)
141
+
142
+ def _get_client_state(self, module: str) -> tuple[ModuleState, str] | None:
143
+ """Return presence state of a connected module."""
144
+ try:
145
+ remote = self._network.get_client(module)
146
+ return remote._presence
147
+ except KeyError:
148
+ return None
149
+
150
+ async def _subscribe_presence(self, module: str, callback: PresenceCallback) -> None:
151
+ """Register a presence callback and deliver the current state immediately."""
152
+ self._presence_callbacks.setdefault(module, []).append(callback)
153
+ try:
154
+ remote = self._network.get_client(module)
155
+ callback(*remote._presence)
156
+ except KeyError:
157
+ pass