pyobs-core 2.14.2__tar.gz → 2.16.0__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 (477) hide show
  1. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/PKG-INFO +1 -1
  2. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/comm.py +8 -10
  3. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/rpc.py +58 -8
  4. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/xmppcomm.py +83 -10
  5. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/__init__.py +4 -1
  6. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/event.py +37 -21
  7. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IPointingAltAz.py +2 -1
  8. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IPointingRaDec.py +2 -1
  9. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/robotic/mastermind.py +38 -0
  10. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/robotic/scheduler.py +1 -1
  11. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/telescope/basetelescope.py +3 -2
  12. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/weather/weather.py +1 -1
  13. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/observation.py +15 -8
  14. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/constraints/airmassconstraint.py +5 -1
  15. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/constraints/constraint.py +32 -1
  16. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/constraints/moonilluminationconstraint.py +8 -2
  17. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/constraints/moonseparationconstraint.py +5 -1
  18. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/constraints/solarelevationconstraint.py +9 -1
  19. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/constraints/timeconstraint.py +5 -1
  20. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/dataprovider.py +26 -5
  21. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/follow.py +2 -2
  22. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/pernight.py +6 -4
  23. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/observationarchiveevolution.py +20 -22
  24. pyobs_core-2.16.0/pyobs/robotic/scheduler/observingperiod.py +89 -0
  25. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/ondemandscheduler.py +11 -1
  26. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/exceptions.py +1 -1
  27. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/exceptions.py +25 -6
  28. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyproject.toml +1 -1
  29. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/.gitignore +0 -0
  30. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/LICENSE +0 -0
  31. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/README.md +0 -0
  32. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/__init__.py +0 -0
  33. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/application.py +0 -0
  34. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/background_task.py +0 -0
  35. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/cli/__init__.py +0 -0
  36. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/cli/_cli.py +0 -0
  37. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/cli/pyobs.py +0 -0
  38. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/cli/pyobsd.py +0 -0
  39. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/cli/pyobsw.py +0 -0
  40. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/__init__.py +0 -0
  41. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/commlogging.py +0 -0
  42. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/dummy/__init__.py +0 -0
  43. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/dummy/dummycomm.py +0 -0
  44. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/local/__init__.py +0 -0
  45. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/local/localcomm.py +0 -0
  46. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/local/localnetwork.py +0 -0
  47. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/proxy.py +0 -0
  48. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/__init__.py +0 -0
  49. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/serializer.py +0 -0
  50. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/xep_0009/__init__.py +0 -0
  51. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/xep_0009/binding.py +0 -0
  52. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/xep_0009/rpc.py +0 -0
  53. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/xep_0009_timeout/__init__.py +0 -0
  54. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/xep_0009_timeout/rpc.py +0 -0
  55. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/xep_0009_timeout/stanza/RPC.py +0 -0
  56. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/xep_0009_timeout/stanza/__init__.py +0 -0
  57. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/comm/xmpp/xmppclient.py +0 -0
  58. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/badweather.py +0 -0
  59. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/exposurestatuschanged.py +0 -0
  60. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/filterchanged.py +0 -0
  61. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/focusfound.py +0 -0
  62. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/goodweather.py +0 -0
  63. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/log.py +0 -0
  64. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/modechanged.py +0 -0
  65. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/moduleclosed.py +0 -0
  66. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/moduleopened.py +0 -0
  67. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/motionstatuschanged.py +0 -0
  68. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/move.py +0 -0
  69. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/newimage.py +0 -0
  70. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/newspectrum.py +0 -0
  71. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/offsets.py +0 -0
  72. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/roofclosing.py +0 -0
  73. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/roofopened.py +0 -0
  74. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/taskfailed.py +0 -0
  75. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/taskfinished.py +0 -0
  76. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/taskskipped.py +0 -0
  77. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/taskstarted.py +0 -0
  78. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/events/testevent.py +0 -0
  79. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/__init__.py +0 -0
  80. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/image.py +0 -0
  81. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/meta/__init__.py +0 -0
  82. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/meta/altazoffsets.py +0 -0
  83. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/meta/datapipeline.py +0 -0
  84. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/meta/exptime.py +0 -0
  85. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/meta/genericoffset.py +0 -0
  86. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/meta/onskydistance.py +0 -0
  87. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/meta/pixeloffsets.py +0 -0
  88. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/meta/radecoffsets.py +0 -0
  89. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/meta/skyoffsets.py +0 -0
  90. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processor.py +0 -0
  91. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/__init__.py +0 -0
  92. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/_daobackgroundremover.py +0 -0
  93. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/annotation/__init__.py +0 -0
  94. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/annotation/_pillow.py +0 -0
  95. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/annotation/circle.py +0 -0
  96. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/annotation/crosshair.py +0 -0
  97. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/annotation/text.py +0 -0
  98. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/astrometry/__init__.py +0 -0
  99. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/astrometry/_dotnet_request.py +0 -0
  100. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/astrometry/_dotnet_request_builder.py +0 -0
  101. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/astrometry/_dotnet_request_logger.py +0 -0
  102. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/astrometry/_dotnet_response_saver.py +0 -0
  103. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/astrometry/astrometry.py +0 -0
  104. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/astrometry/dotnet.py +0 -0
  105. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/calibration/__init__.py +0 -0
  106. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/calibration/_calibration_cache.py +0 -0
  107. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/calibration/_ccddata_calibrator.py +0 -0
  108. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/calibration/calibration.py +0 -0
  109. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/detection/__init__.py +0 -0
  110. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/detection/_pysep_stats_calculator.py +0 -0
  111. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/detection/_source_catalog.py +0 -0
  112. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/detection/daophot.py +0 -0
  113. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/detection/pysep.py +0 -0
  114. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/detection/simpledisk.py +0 -0
  115. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/detection/sourcedetection.py +0 -0
  116. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/exptime/__init__.py +0 -0
  117. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/exptime/exptime.py +0 -0
  118. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/exptime/star.py +0 -0
  119. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/__init__.py +0 -0
  120. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/addfitsheaders.py +0 -0
  121. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/download.py +0 -0
  122. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/flip.py +0 -0
  123. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/grayscale.py +0 -0
  124. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/httpserver.py +0 -0
  125. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/normalize.py +0 -0
  126. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/save.py +0 -0
  127. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/saveimage.py +0 -0
  128. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/smooth.py +0 -0
  129. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/image/softbin.py +0 -0
  130. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/misc/__init__.py +0 -0
  131. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/misc/addmask.py +0 -0
  132. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/misc/catalogcircularmask.py +0 -0
  133. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/misc/circularmask.py +0 -0
  134. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/misc/createfilename.py +0 -0
  135. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/misc/imagesourcefilter.py +0 -0
  136. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/misc/removebackground.py +0 -0
  137. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/modules/__init__.py +0 -0
  138. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/modules/getfitsheaders.py +0 -0
  139. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/__init__.py +0 -0
  140. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/astrometry.py +0 -0
  141. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/brighteststar.py +0 -0
  142. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/brighteststar_guiding.py +0 -0
  143. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/dummyoffsets.py +0 -0
  144. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/dummyskyoffsets.py +0 -0
  145. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/fitsheader.py +0 -0
  146. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/offsets.py +0 -0
  147. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/pixeloffsets.py +0 -0
  148. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/projected.py +0 -0
  149. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/offsets/spilled_light.py +0 -0
  150. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/photometry/__init__.py +0 -0
  151. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/photometry/_photometry_calculator.py +0 -0
  152. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/photometry/_photutil_aperture_photometry.py +0 -0
  153. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/photometry/_sep_aperture_photometry.py +0 -0
  154. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/photometry/aperture_photometry.py +0 -0
  155. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/photometry/photometry.py +0 -0
  156. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/photometry/photutil.py +0 -0
  157. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/photometry/pysep.py +0 -0
  158. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/stack/__init__.py +0 -0
  159. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/stack/combinestack.py +0 -0
  160. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/stack/mean.py +0 -0
  161. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/stack/median.py +0 -0
  162. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/stack/sigmaclip.py +0 -0
  163. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/stack/sum.py +0 -0
  164. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/wcs/__init__.py +0 -0
  165. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/images/processors/wcs/solarhelioprojective.py +0 -0
  166. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IAbortable.py +0 -0
  167. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IAcquisition.py +0 -0
  168. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IAutoFocus.py +0 -0
  169. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IAutoGuiding.py +0 -0
  170. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IAutonomous.py +0 -0
  171. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IBinning.py +0 -0
  172. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/ICalibrate.py +0 -0
  173. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/ICamera.py +0 -0
  174. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IConfig.py +0 -0
  175. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/ICooling.py +0 -0
  176. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IData.py +0 -0
  177. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IDataPipeline.py +0 -0
  178. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IDataSequence.py +0 -0
  179. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IDataStack.py +0 -0
  180. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IDome.py +0 -0
  181. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IExposure.py +0 -0
  182. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IExposureTime.py +0 -0
  183. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IFilters.py +0 -0
  184. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IFitsHeaderAfter.py +0 -0
  185. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IFitsHeaderBefore.py +0 -0
  186. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IFlatField.py +0 -0
  187. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IFocusModel.py +0 -0
  188. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IFocuser.py +0 -0
  189. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IGain.py +0 -0
  190. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IImageFormat.py +0 -0
  191. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IImageType.py +0 -0
  192. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IMode.py +0 -0
  193. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IModule.py +0 -0
  194. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IMotion.py +0 -0
  195. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IMultiFiber.py +0 -0
  196. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IOffsetsAltAz.py +0 -0
  197. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IOffsetsRaDec.py +0 -0
  198. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IPointingBody.py +0 -0
  199. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IPointingHeliocentricPolar.py +0 -0
  200. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IPointingHeliographicStonyhurst.py +0 -0
  201. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IPointingHelioprojective.py +0 -0
  202. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IPointingOrbitalElements.py +0 -0
  203. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IPointingSeries.py +0 -0
  204. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IPushNotifications.py +0 -0
  205. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IReady.py +0 -0
  206. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IResettable.py +0 -0
  207. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IRobotic.py +0 -0
  208. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IRoboticScheduler.py +0 -0
  209. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IRoof.py +0 -0
  210. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IRotation.py +0 -0
  211. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IRunnable.py +0 -0
  212. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IRunning.py +0 -0
  213. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IScriptRunner.py +0 -0
  214. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/ISpectrograph.py +0 -0
  215. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IStartStop.py +0 -0
  216. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IStructuredConfig.py +0 -0
  217. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/ISyncTarget.py +0 -0
  218. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/ITelescope.py +0 -0
  219. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/ITemperatures.py +0 -0
  220. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/ITrackingMode.py +0 -0
  221. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/ITrackingRate.py +0 -0
  222. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IVideo.py +0 -0
  223. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IWeather.py +0 -0
  224. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/IWindow.py +0 -0
  225. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/__init__.py +0 -0
  226. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/interfaces/interface.py +0 -0
  227. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/__init__.py +0 -0
  228. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/camerasettings.py +0 -0
  229. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/datapipeline.py +0 -0
  230. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/datasequence.py +0 -0
  231. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/fitsheader.py +0 -0
  232. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/fitsnamespace.py +0 -0
  233. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/follow.py +0 -0
  234. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/motionstatus.py +0 -0
  235. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/pipeline.py +0 -0
  236. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/waitformotion.py +0 -0
  237. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/mixins/weatheraware.py +0 -0
  238. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/__init__.py +0 -0
  239. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/__init__.py +0 -0
  240. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/adaptive.py +0 -0
  241. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/basecamera.py +0 -0
  242. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/basespectrograph.py +0 -0
  243. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/basevideo.py +0 -0
  244. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/dummycamera.py +0 -0
  245. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/dummyspectrograph.py +0 -0
  246. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/dummyvideo.py +0 -0
  247. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/pipelinecamera.py +0 -0
  248. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/camera/videoframes.py +0 -0
  249. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/flatfield/__init__.py +0 -0
  250. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/flatfield/flatfield.py +0 -0
  251. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/flatfield/pointing.py +0 -0
  252. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/flatfield/scheduler.py +0 -0
  253. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/focus/__init__.py +0 -0
  254. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/focus/dummyautofocus.py +0 -0
  255. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/focus/focusmodel.py +0 -0
  256. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/focus/focusseries.py +0 -0
  257. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/image/__init__.py +0 -0
  258. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/image/imagewatcher.py +0 -0
  259. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/image/imagewriter.py +0 -0
  260. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/image/pipeline.py +0 -0
  261. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/image/seeing.py +0 -0
  262. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/module.py +0 -0
  263. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/__init__.py +0 -0
  264. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/_base.py +0 -0
  265. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/_baseguiding.py +0 -0
  266. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/acquisition.py +0 -0
  267. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/autoguiding.py +0 -0
  268. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/dummyacquisition.py +0 -0
  269. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/dummyguiding.py +0 -0
  270. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/guidingsource.py +0 -0
  271. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/guidingstatistics/__init__.py +0 -0
  272. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/guidingstatistics/guidingstatistics.py +0 -0
  273. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/guidingstatistics/pixeloffset.py +0 -0
  274. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/guidingstatistics/skyoffsets.py +0 -0
  275. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/guidingstatistics/uptime.py +0 -0
  276. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/pointing/scienceframeguiding.py +0 -0
  277. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/robotic/__init__.py +0 -0
  278. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/robotic/dummymastermind.py +0 -0
  279. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/robotic/dummyscheduler.py +0 -0
  280. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/robotic/pointing.py +0 -0
  281. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/robotic/scriptrunner.py +0 -0
  282. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/roof/__init__.py +0 -0
  283. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/roof/basedome.py +0 -0
  284. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/roof/baseroof.py +0 -0
  285. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/roof/dummyroof.py +0 -0
  286. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/telescope/__init__.py +0 -0
  287. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/telescope/_dummytelescopebase.py +0 -0
  288. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/telescope/dummyaltaztelescope.py +0 -0
  289. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/telescope/dummyradectelescope.py +0 -0
  290. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/telescope/dummysolartelescope.py +0 -0
  291. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/test/__init__.py +0 -0
  292. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/test/standalone.py +0 -0
  293. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/__init__.py +0 -0
  294. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/dummymode.py +0 -0
  295. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/dummystructuredconfig.py +0 -0
  296. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/fluentlogger.py +0 -0
  297. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/httpfilecache.py +0 -0
  298. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/kiosk.py +0 -0
  299. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/matrix.py +0 -0
  300. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/pushnotifier.py +0 -0
  301. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/stellarium.py +0 -0
  302. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/telegram.py +0 -0
  303. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/utils/trigger.py +0 -0
  304. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/weather/__init__.py +0 -0
  305. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/weather/mockweather.py +0 -0
  306. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/weather/weather_api.py +0 -0
  307. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/modules/weather/weather_state.py +0 -0
  308. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/object.py +0 -0
  309. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/py.typed +0 -0
  310. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/__init__.py +0 -0
  311. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/instruments.py +0 -0
  312. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/__init__.py +0 -0
  313. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/_executor.py +0 -0
  314. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/astroplanscheduler.py +0 -0
  315. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/constraints/__init__.py +0 -0
  316. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/__init__.py +0 -0
  317. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/aftertime.py +0 -0
  318. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/beforetime.py +0 -0
  319. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/constant.py +0 -0
  320. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/interval.py +0 -0
  321. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/merit.py +0 -0
  322. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/random.py +0 -0
  323. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/timewindow.py +0 -0
  324. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/merits/transit.py +0 -0
  325. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/targets/__init__.py +0 -0
  326. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/targets/dynamictarget.py +0 -0
  327. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/targets/heliocentricpolartarget.py +0 -0
  328. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/targets/helioprojectivetarget.py +0 -0
  329. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/targets/picker/__init__.py +0 -0
  330. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/targets/picker/csvpicker.py +0 -0
  331. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/targets/picker/picker.py +0 -0
  332. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/targets/siderealtarget.py +0 -0
  333. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/targets/target.py +0 -0
  334. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scheduler/taskscheduler.py +0 -0
  335. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/__init__.py +0 -0
  336. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/calibration/__init__.py +0 -0
  337. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/calibration/darkbias.py +0 -0
  338. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/calibration/pointing.py +0 -0
  339. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/calibration/skyflats.py +0 -0
  340. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/control/__init__.py +0 -0
  341. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/control/cases.py +0 -0
  342. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/control/conditional.py +0 -0
  343. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/control/parallel.py +0 -0
  344. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/control/selector.py +0 -0
  345. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/control/sequential.py +0 -0
  346. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/imaging/__init__.py +0 -0
  347. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/imaging/autofocus.py +0 -0
  348. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/imaging/imaging.py +0 -0
  349. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/imaging/transitimaging.py +0 -0
  350. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/script.py +0 -0
  351. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/utils/__init__.py +0 -0
  352. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/utils/callmodule.py +0 -0
  353. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/utils/debugtrigger.py +0 -0
  354. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/scripts/utils/log.py +0 -0
  355. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/__init__.py +0 -0
  356. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/filesystem/__init__.py +0 -0
  357. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/filesystem/observationarchive.py +0 -0
  358. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/filesystem/taskarchive.py +0 -0
  359. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/__init__.py +0 -0
  360. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/_portal.py +0 -0
  361. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/_schedulereader.py +0 -0
  362. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/_schedulewriter.py +0 -0
  363. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/configdb.py +0 -0
  364. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/mockobservationarchive.py +0 -0
  365. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/observationarchive.py +0 -0
  366. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/scripts/__init__.py +0 -0
  367. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/scripts/autofocus.py +0 -0
  368. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/scripts/default.py +0 -0
  369. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/scripts/script.py +0 -0
  370. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/task.py +0 -0
  371. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/taskarchive.py +0 -0
  372. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/lco/taskrunner.py +0 -0
  373. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/memory/__init__.py +0 -0
  374. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/memory/observationarchive.py +0 -0
  375. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/memory/taskarchive.py +0 -0
  376. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/observationarchive.py +0 -0
  377. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/portal/__init__.py +0 -0
  378. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/portal/observationarchive.py +0 -0
  379. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/portal/taskarchive.py +0 -0
  380. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/storage/taskarchive.py +0 -0
  381. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/task.py +0 -0
  382. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/taskrunner.py +0 -0
  383. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/__init__.py +0 -0
  384. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/archive/__init__.py +0 -0
  385. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/archive/archive.py +0 -0
  386. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/archive/local_archive.py +0 -0
  387. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/archive/pyobs_archive.py +0 -0
  388. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/calibration.py +0 -0
  389. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/exptime/__init__.py +0 -0
  390. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/exptime/exptime.py +0 -0
  391. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/exptime/stellarexptime.py +0 -0
  392. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/__init__.py +0 -0
  393. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/exptimeeval.py +0 -0
  394. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/flatfielder.py +0 -0
  395. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/pointing/__init__.py +0 -0
  396. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/pointing/base.py +0 -0
  397. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/pointing/static.py +0 -0
  398. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/priorities/__init__.py +0 -0
  399. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/priorities/archive.py +0 -0
  400. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/priorities/base.py +0 -0
  401. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/priorities/const.py +0 -0
  402. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/robotic/utils/skyflats/scheduler.py +0 -0
  403. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/__init__.py +0 -0
  404. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/average.py +0 -0
  405. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/cache.py +0 -0
  406. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/config.py +0 -0
  407. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/config_schema.py +0 -0
  408. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/coordinates.py +0 -0
  409. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/curvefit.py +0 -0
  410. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/enums.py +0 -0
  411. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/exptime_grouping.py +0 -0
  412. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/fits.py +0 -0
  413. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/focusseries/__init__.py +0 -0
  414. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/focusseries/base.py +0 -0
  415. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/focusseries/photometry.py +0 -0
  416. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/focusseries/projection.py +0 -0
  417. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/grids/__init__.py +0 -0
  418. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/grids/filters.py +0 -0
  419. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/grids/grid.py +0 -0
  420. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/grids/gridnode.py +0 -0
  421. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/grids/pipeline.py +0 -0
  422. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/gui/__init__.py +0 -0
  423. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/gui/camera/__init__.py +0 -0
  424. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/gui/camera/binningwidget.py +0 -0
  425. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/gui/camera/datadisplaywidget.py +0 -0
  426. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/gui/camera/exposewidget.py +0 -0
  427. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/gui/camera/exposuretimewidget.py +0 -0
  428. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/gui/camera/imageformatwidget.py +0 -0
  429. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/gui/camera/listpickerdialog.py +0 -0
  430. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/gui/camera/windowingwidget.py +0 -0
  431. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/http.py +0 -0
  432. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/influxdb.py +0 -0
  433. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/logger.py +0 -0
  434. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/logging/__init__.py +0 -0
  435. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/logging/context.py +0 -0
  436. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/logging/resolvableerror.py +0 -0
  437. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/modulegui.py +0 -0
  438. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/offsets/__init__.py +0 -0
  439. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/offsets/applyaltazoffsets.py +0 -0
  440. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/offsets/applyoffsets.py +0 -0
  441. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/offsets/applyradecoffsets.py +0 -0
  442. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/parallel.py +0 -0
  443. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/pipeline/__init__.py +0 -0
  444. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/pipeline/pipeline.py +0 -0
  445. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/pipeline/progress.py +0 -0
  446. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/pipeline/reduction.py +0 -0
  447. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/pipeline/reduction_base.py +0 -0
  448. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/publisher/__init__.py +0 -0
  449. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/publisher/csv.py +0 -0
  450. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/publisher/http.py +0 -0
  451. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/publisher/log.py +0 -0
  452. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/publisher/multi.py +0 -0
  453. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/publisher/publisher.py +0 -0
  454. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/serialization.py +0 -0
  455. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/shellcommand.py +0 -0
  456. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/stretch.py +0 -0
  457. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/threads/__init__.py +0 -0
  458. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/threads/lockwithabort.py +0 -0
  459. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/time.py +0 -0
  460. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/units.py +0 -0
  461. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/utils/versions.py +0 -0
  462. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/version.py +0 -0
  463. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/__init__.py +0 -0
  464. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/archivefile.py +0 -0
  465. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/bufferedfile.py +0 -0
  466. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/file.py +0 -0
  467. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/filelists/__init__.py +0 -0
  468. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/filelists/filelist.py +0 -0
  469. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/filelists/testing.py +0 -0
  470. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/httpfile.py +0 -0
  471. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/localfile.py +0 -0
  472. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/memfile.py +0 -0
  473. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/sftpfile.py +0 -0
  474. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/smbfile.py +0 -0
  475. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/sshfile.py +0 -0
  476. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/tempfile.py +0 -0
  477. {pyobs_core-2.14.2 → pyobs_core-2.16.0}/pyobs/vfs/vfs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pyobs-core
3
- Version: 2.14.2
3
+ Version: 2.16.0
4
4
  Summary: robotic telescope software
5
5
  Project-URL: Homepage, https://www.pyobs.org
6
6
  Project-URL: Documentation, https://docs.pyobs.org
@@ -2,12 +2,11 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import functools
5
- import inspect
6
5
  import logging
7
6
  from collections.abc import Callable, Coroutine
8
7
  from typing import TYPE_CHECKING, Any, overload
9
8
 
10
- from pyobs.events import Event, LogEvent, ModuleClosedEvent
9
+ from pyobs.events import Event, LogEvent, ModuleClosedEvent, registered_events
11
10
  from pyobs.interfaces import Interface
12
11
  from pyobs.interfaces.interface import get_registered_interface
13
12
  from pyobs.utils.enums import ModuleState
@@ -151,6 +150,10 @@ class Comm:
151
150
  if interface.has_own_state():
152
151
  await self.subscribe_state(client, interface, functools.partial(proxy.update_state, interface))
153
152
 
153
+ # a proxy without interfaces is useless and would stay stale (e.g. peer's disco#info not
154
+ # answered yet), so do not cache it
155
+ if len(interfaces) == 0:
156
+ return proxy
154
157
  self._proxies[client] = proxy
155
158
 
156
159
  return self._proxies[client]
@@ -416,17 +419,12 @@ class Comm:
416
419
  Returns:
417
420
  list of event classes.
418
421
  """
419
- import pyobs.events
420
-
421
- event_classes: list[type[Event]] = []
422
- for cls in inspect.getmembers(pyobs.events, inspect.isclass):
423
- if issubclass(cls[1], event):
424
- event_classes.append(cls[1])
425
- return event_classes
422
+ return [cls for cls in registered_events().values() if issubclass(cls, event)]
426
423
 
427
424
  async def register_event(self, event_class: type[Event], handler: EventHandler | None = None) -> None:
428
425
  """Register an event type. If a handler is given, we also receive those events, otherwise we just
429
- send them.
426
+ send them. Event classes derived from the given one are registered, too, but only those that have been
427
+ imported (and therefore registered) by now.
430
428
 
431
429
  Args:
432
430
  event_class: Class of event to register.
@@ -114,6 +114,22 @@ def fault_to_xml(exception: Exception) -> ET.Element:
114
114
  msg_elem.text = getattr(exception, "message", None) or str(exception)
115
115
  pyobs_fault.append(exc_elem)
116
116
  pyobs_fault.append(msg_elem)
117
+
118
+ # PyobsError ancestors (nearest first), so a caller that never imported the exception's own module can
119
+ # still raise the nearest class it knows (e.g. MotionError). PyobsError itself and UnclassifiedError carry
120
+ # no information. If original_type was sent above, the exception itself is already an ancestor-resolved
121
+ # class (second hop), which the name no longer tells, so it goes first.
122
+ candidates = type(exception).__mro__ if original_type else type(exception).__mro__[1:]
123
+ bases = [
124
+ c
125
+ for c in candidates
126
+ if isinstance(c, type) and issubclass(c, exc.PyobsError) and c not in (exc.PyobsError, exc.UnclassifiedError)
127
+ ]
128
+ bases_elem = ET.Element("bases")
129
+ for c in bases:
130
+ base_elem = ET.SubElement(bases_elem, "base")
131
+ base_elem.text = f"{c.__module__}.{c.__qualname__}"
132
+ pyobs_fault.append(bases_elem)
117
133
  value_elem.append(pyobs_fault)
118
134
  fault.append(value_elem)
119
135
  return fault
@@ -135,6 +151,47 @@ def xml_to_fault(fault_elem: ET.Element) -> tuple[str, str]:
135
151
  return exc_name, msg
136
152
 
137
153
 
154
+ def xml_to_fault_bases(fault_elem: ET.Element) -> list[str]:
155
+ """Parse <fault> and return the qualified names of the exception's PyobsError ancestors, nearest first.
156
+
157
+ Empty if the fault has none, e.g. when it comes from a peer that does not send them.
158
+ """
159
+ value_elem = fault_elem.find(f"{{{_NS}}}value")
160
+ if value_elem is None:
161
+ return []
162
+ pyobs_fault = value_elem.find(f"{{{_PYOBS_NS}}}fault")
163
+ if pyobs_fault is None:
164
+ return []
165
+ return [b.text for b in pyobs_fault.iterfind("bases/base") if b.text]
166
+
167
+
168
+ def reconstruct_fault(name: str, message: str, bases: list[str], remote_module: str) -> exc.PyobsError:
169
+ """Build the exception for a received fault.
170
+
171
+ The exact class is used if it is registered here. If not (never a PyobsError, or its defining module was
172
+ never imported in this process), the nearest registered ancestor from `bases` is used, and failing that
173
+ UnclassifiedError. In both fallbacks the name survives as `original_type`.
174
+
175
+ Args:
176
+ name: Qualified name of the exception, as sent.
177
+ message: Message of the exception.
178
+ bases: Qualified names of its PyobsError ancestors, nearest first.
179
+ remote_module: Name of the module the fault came from.
180
+
181
+ Returns:
182
+ The exception to raise.
183
+ """
184
+ exception_class = exc.PyobsError.resolve(name)
185
+ if exception_class is not None:
186
+ # real registered type (e.g. FocusError) -- raised as itself, not wrapped
187
+ return exception_class(message, remote_module=remote_module)
188
+ for base in bases:
189
+ ancestor = exc.PyobsError.resolve(base)
190
+ if ancestor is not None and ancestor is not exc.UnclassifiedError:
191
+ return ancestor(message, original_type=name, remote_module=remote_module)
192
+ return exc.UnclassifiedError(message, original_type=name, remote_module=remote_module)
193
+
194
+
138
195
  # ---------------------------------------------------------------------------
139
196
  # RPC class
140
197
  # ---------------------------------------------------------------------------
@@ -291,14 +348,7 @@ class RPC:
291
348
  exc_name, msg = xml_to_fault(fault_elem)
292
349
 
293
350
  sender = iq["from"].node
294
- exception_class = exc.PyobsError.resolve(exc_name)
295
- if exception_class is not None:
296
- # real registered type (e.g. FocusError) -- raised as itself, not wrapped
297
- exception: exc.PyobsError = exception_class(msg, remote_module=sender)
298
- else:
299
- # unresolvable: never a PyobsError to begin with, or its defining module was never
300
- # imported in this process -- the qualified name string still survives as original_type
301
- exception = exc.UnclassifiedError(msg, original_type=exc_name, remote_module=sender)
351
+ exception = reconstruct_fault(exc_name, msg, xml_to_fault_bases(fault_elem), sender)
302
352
 
303
353
  # same correlation id Module.execute() logged on the origin side -- an operator can jump
304
354
  # straight from this caller-side exception to the matching detailed log line, by id
@@ -223,6 +223,7 @@ class XmppComm(Comm):
223
223
  self._interface_cache: dict[str, asyncio.Future[list[type[Interface]]]] = {}
224
224
  self._interface_features: dict[str, list[str]] = {}
225
225
  self._peer_sent_events: dict[str, set[tuple[str, int]]] = {}
226
+ self._discovery_retries: dict[str, asyncio.Task[None]] = {}
226
227
  self._warned_version_mismatches: set[tuple[str, str]] = set()
227
228
  self._user = user
228
229
  self._password = password
@@ -436,8 +437,10 @@ class XmppComm(Comm):
436
437
  self._capabilities = {} # clear capabilities on reconnect
437
438
 
438
439
  # disconnect all clients
439
- for jid in self._online_clients:
440
+ for jid in list(self._online_clients):
440
441
  self._jid_got_offline(jid)
442
+ for jid in list(self._discovery_retries):
443
+ self._cancel_discovery_retry(jid)
441
444
 
442
445
  if client.kicked_by_conflict:
443
446
  # another session took over our JID/resource -- reconnecting would just
@@ -508,32 +511,35 @@ class XmppComm(Comm):
508
511
  # return them from cache
509
512
  return await self._interface_cache[client]
510
513
 
511
- async def _get_interfaces(self, jid: str, attempts: int = 3) -> list[str]:
514
+ async def _get_interfaces(self, jid: str, attempts: int = 3) -> list[str] | None:
512
515
  """Return list of interfaces for the given JID.
513
516
 
514
517
  Args:
515
518
  jid: JID to get interfaces for.
516
519
 
517
520
  Returns:
518
- List of interface names or empty list, if an error occurred.
521
+ List of interface names, empty if the peer answered but does not implement IModule, or None
522
+ if disco#info itself failed (error, timeout, no or malformed answer).
519
523
  """
520
524
 
521
525
  # request features
522
526
  try:
523
527
  info = await self._safe_send(self.client.plugin["xep_0030"].get_info, jid=jid, cached=False)
524
- except (slixmpp.exceptions.IqError, slixmpp.exceptions.IqTimeout):
525
- return []
528
+ except (slixmpp.exceptions.IqError, slixmpp.exceptions.IqTimeout) as e:
529
+ log.warning("disco#info for %s failed (%s).", jid, _iq_failure_reason(e))
530
+ return None
526
531
 
527
532
  # extract pyobs interface names
528
533
  if info is None:
529
- return []
534
+ return None
530
535
  try:
531
536
  if isinstance(info, slixmpp.stanza.iq.Iq):
532
537
  info = info["disco_info"]
533
538
  prefix = "urn:pyobs:interface:"
534
539
  features = [i for i in info["features"] if i.startswith(prefix)]
535
540
  except TypeError:
536
- raise IndexError()
541
+ log.warning("Malformed disco#info answer from %s.", jid)
542
+ return None
537
543
 
538
544
  # cache raw features for this JID, so a later version-mismatch can be diagnosed
539
545
  self._interface_features[jid] = features
@@ -563,7 +569,10 @@ class XmppComm(Comm):
563
569
  return []
564
570
  else:
565
571
  await asyncio.sleep(5)
566
- interface_names = await self._get_interfaces(jid, attempts - 1)
572
+ retried = await self._get_interfaces(jid, attempts - 1)
573
+ if retried is None:
574
+ return None
575
+ interface_names = retried
567
576
 
568
577
  # finished
569
578
  return interface_names
@@ -705,6 +714,55 @@ class XmppComm(Comm):
705
714
  if jid == self._jid:
706
715
  return
707
716
 
717
+ # try to get the peer online; if its disco#info failed, keep trying in the background, since
718
+ # slixmpp won't fire got_online again until the peer went offline and came back
719
+ if await self._process_online(msg):
720
+ self._cancel_discovery_retry(jid)
721
+ elif jid not in self._discovery_retries:
722
+ task = asyncio.create_task(self._retry_discovery(msg))
723
+ self._discovery_retries[jid] = task
724
+ task.add_done_callback(_log_task_exception)
725
+
726
+ def _cancel_discovery_retry(self, jid: str) -> None:
727
+ """Cancel a pending background disco#info retry for the given JID, if any."""
728
+ task = self._discovery_retries.pop(jid, None)
729
+ if task is not None and task is not asyncio.current_task():
730
+ task.cancel()
731
+
732
+ async def _retry_discovery(self, msg: Any) -> None:
733
+ """Repeat the disco#info of a peer that failed it, until it works or the peer is gone.
734
+
735
+ Args:
736
+ msg: Presence message of the peer.
737
+ """
738
+ jid = msg["from"].full
739
+ try:
740
+ attempt = 0
741
+ while jid not in self._online_clients:
742
+ attempt += 1
743
+ if attempt == 30:
744
+ log.warning(
745
+ "Still failing to get disco#info for %s after %d attempts, will keep retrying", jid, attempt
746
+ )
747
+ await asyncio.sleep(_retry_delay(attempt))
748
+ if await self._process_online(msg):
749
+ log.info("Got disco#info for %s after %d retries.", jid, attempt)
750
+ return
751
+ finally:
752
+ if self._discovery_retries.get(jid) is asyncio.current_task():
753
+ del self._discovery_retries[jid]
754
+
755
+ async def _process_online(self, msg: Any) -> bool:
756
+ """Get interfaces of a peer that came online and register it.
757
+
758
+ Args:
759
+ msg: XMPP presence message.
760
+
761
+ Returns:
762
+ False if disco#info failed and it should be tried again, True otherwise.
763
+ """
764
+ jid = msg["from"].full
765
+
708
766
  # clear interface cache, just in case there is something there
709
767
  if jid in self._interface_cache:
710
768
  del self._interface_cache[jid]
@@ -715,6 +773,16 @@ class XmppComm(Comm):
715
773
  # request interfaces
716
774
  interface_names = await self._get_interfaces(jid)
717
775
 
776
+ # disco#info failed: fail waiting get_interfaces() calls instead of reporting "no interfaces"
777
+ if interface_names is None:
778
+ if jid not in self._discovery_retries:
779
+ log.warning("disco#info for %s failed, retrying in background.", jid)
780
+ future = self._interface_cache.pop(jid, None)
781
+ if future is not None and not future.done():
782
+ future.set_exception(IndexError(f"disco#info for {jid} failed."))
783
+ future.exception() # mark as retrieved, there may be no waiter
784
+ return False
785
+
718
786
  # if no interfaces are implemented (not even IModule), quit here
719
787
  if len(interface_names) == 0:
720
788
  module = jid[: jid.index("@")]
@@ -724,7 +792,7 @@ class XmppComm(Comm):
724
792
  future = self._interface_cache.get(jid)
725
793
  if future is not None and not future.done():
726
794
  future.set_result([])
727
- return
795
+ return True
728
796
 
729
797
  # store interfaces — guard against a second _got_online for the same JID
730
798
  # (ejabberd may send multiple presence stanzas) racing against the first
@@ -775,6 +843,7 @@ class XmppComm(Comm):
775
843
 
776
844
  # send event
777
845
  self._send_event_to_module(ModuleOpenedEvent(), msg["from"].username)
846
+ return True
778
847
 
779
848
  def _fire_presence_callbacks(self, module_name: str, state: ModuleState, status: str) -> None:
780
849
  """Call every presence callback registered for a module, isolating failures.
@@ -845,6 +914,8 @@ class XmppComm(Comm):
845
914
  if jid in self._interface_cache:
846
915
  del self._interface_cache[jid]
847
916
  self._interface_features.pop(jid, None)
917
+ self._peer_sent_events.pop(jid, None)
918
+ self._cancel_discovery_retry(jid)
848
919
 
849
920
  # send event
850
921
  self._send_event_to_module(ModuleClosedEvent(), module_name)
@@ -1106,9 +1177,11 @@ class XmppComm(Comm):
1106
1177
  event = EventFactory.from_dict(body)
1107
1178
  if event is None:
1108
1179
  return
1109
- if time.time() - event.timestamp > 30:
1180
+ age = time.time() - event.timestamp
1181
+ if age > 30:
1110
1182
  # event is more than 30 seconds old, ignore it
1111
1183
  # we do this do avoid resent events after a reconnect
1184
+ log.info("Dropping %s from %s, %.1f s old.", type(event).__name__, from_module, age)
1112
1185
  return
1113
1186
 
1114
1187
  # send it to module
@@ -5,7 +5,7 @@ TODO: write doc
5
5
  __title__ = "Events"
6
6
 
7
7
  from .badweather import BadWeatherEvent
8
- from .event import Event
8
+ from .event import Event, EventFactory, get_registered_event, registered_events
9
9
  from .exposurestatuschanged import ExposureStatusChangedEvent
10
10
  from .filterchanged import FilterChangedEvent
11
11
  from .focusfound import FocusFoundEvent
@@ -31,6 +31,9 @@ __all__ = [
31
31
  "BadWeatherEvent",
32
32
  "ExposureStatusChangedEvent",
33
33
  "Event",
34
+ "EventFactory",
35
+ "get_registered_event",
36
+ "registered_events",
34
37
  "FilterChangedEvent",
35
38
  "FocusFoundEvent",
36
39
  "GoodWeatherEvent",
@@ -8,14 +8,34 @@ from typing import Any
8
8
 
9
9
  log = logging.getLogger(__name__)
10
10
 
11
+ _REGISTRY: dict[str, type[Event]] = {}
12
+
11
13
 
12
14
  class Event:
13
- """Base class for all events."""
15
+ """Base class for all events.
16
+
17
+ Every subclass is registered by its class name (the wire type and the pubsub node id only carry the
18
+ name), no matter where it is defined, so that plugins can define their own events. Two different classes
19
+ with the same name are rejected.
20
+ """
14
21
 
15
22
  __module__ = "pyobs.events"
16
23
  local: bool = False
17
24
  version: int = 1
18
25
 
26
+ def __init_subclass__(cls, **kwargs: Any) -> None:
27
+ super().__init_subclass__(**kwargs)
28
+
29
+ existing = _REGISTRY.get(cls.__name__)
30
+ if existing is not None and (existing.__module__, existing.__qualname__) != (cls.__module__, cls.__qualname__):
31
+ raise TypeError(
32
+ f"Event name '{cls.__name__}' is already registered by "
33
+ f"{existing.__module__}.{existing.__qualname__}; "
34
+ f"choose a distinct name for {cls.__module__}.{cls.__qualname__}."
35
+ )
36
+ # same module and name: a reload or a redefinition, replace the old class
37
+ _REGISTRY[cls.__name__] = cls
38
+
19
39
  def __init__(self, **kwargs: Any):
20
40
  self.uuid = str(uuid.uuid4())
21
41
  self.timestamp = time.time()
@@ -35,9 +55,20 @@ class Event:
35
55
  return cls(**d)
36
56
 
37
57
 
38
- class EventFactory:
39
- packages = [__package__]
58
+ _REGISTRY["Event"] = Event
59
+
40
60
 
61
+ def get_registered_event(name: str) -> type[Event] | None:
62
+ """Return the event class registered under the given class name, or None."""
63
+ return _REGISTRY.get(name)
64
+
65
+
66
+ def registered_events() -> dict[str, type[Event]]:
67
+ """Return a copy of all registered event classes by name, in registration order."""
68
+ return dict(_REGISTRY)
69
+
70
+
71
+ class EventFactory:
41
72
  @staticmethod
42
73
  def from_dict(obj_dict: dict[str, Any]) -> Event | None:
43
74
  """Create Event from a dictionary.
@@ -49,23 +80,8 @@ class EventFactory:
49
80
  Event object containing event.
50
81
  """
51
82
 
52
- # create class
53
- cls: Event | None = None
54
- for p in EventFactory.packages:
55
- if p is None:
56
- continue
57
- # import package
58
- parts = p.split(".")
59
- pkg = __import__(parts[0])
60
- for comp in parts[1:]:
61
- pkg = getattr(pkg, comp)
62
-
63
- # does it have the given type as class?
64
- if hasattr(pkg, obj_dict["type"]):
65
- cls = getattr(pkg, obj_dict["type"])
66
- break
67
-
68
- # not found?
83
+ # find class, it has to be imported (and therefore registered) in this process
84
+ cls = get_registered_event(obj_dict["type"])
69
85
  if cls is None:
70
86
  return None
71
87
 
@@ -84,4 +100,4 @@ class EventFactory:
84
100
  return None
85
101
 
86
102
 
87
- __all__ = ["Event", "EventFactory"]
103
+ __all__ = ["Event", "EventFactory", "get_registered_event", "registered_events"]
@@ -35,7 +35,8 @@ class IPointingAltAz(Interface, metaclass=ABCMeta):
35
35
 
36
36
  Raises:
37
37
  NotSupportedError: If this device doesn't support Alt/Az pointing.
38
- AltitudeLimitError: If the destination is below the configured altitude limit.
38
+ PointingLimitError: If the destination is outside the pointing limits, e.g. below the configured
39
+ altitude limit (AltitudeLimitError).
39
40
  MoveError: If device could not be moved.
40
41
  """
41
42
  ...
@@ -36,7 +36,8 @@ class IPointingRaDec(Interface, metaclass=ABCMeta):
36
36
  Raises:
37
37
  NotSupportedError: If this device doesn't support RA/Dec pointing.
38
38
  MissingObserverError: If no observer is configured.
39
- AltitudeLimitError: If the destination is below the configured altitude limit.
39
+ PointingLimitError: If the destination is outside the pointing limits, e.g. below the configured
40
+ altitude limit (AltitudeLimitError).
40
41
  MoveError: If device could not be moved.
41
42
  """
42
43
  ...
@@ -63,6 +63,8 @@ class Mastermind(Module, IAutonomous, IRobotic, IFitsHeaderBefore):
63
63
  self._after_task_sleep = after_task_sleep
64
64
  self._cant_run_reason: str | None = None
65
65
  self._next_observation: Observation | None = None
66
+ self._project_priority: float | None = None
67
+ self._observation: Observation | None = None
66
68
 
67
69
  # add thread func
68
70
  self.add_background_task(self._run_thread, True)
@@ -258,7 +260,9 @@ class Mastermind(Module, IAutonomous, IRobotic, IFitsHeaderBefore):
258
260
 
259
261
  # task is definitely not None here
260
262
  self._task = observation.task
263
+ self._observation = observation
261
264
  self._task_target = observation.target
265
+ self._project_priority = await self._lookup_project_priority(observation.task)
262
266
  self._obsnum = await self._next_obsnum()
263
267
 
264
268
  # ETA
@@ -296,6 +300,8 @@ class Mastermind(Module, IAutonomous, IRobotic, IFitsHeaderBefore):
296
300
  await self._observation_archive.update_observation(observation)
297
301
  await self.comm.send_event(TaskFailedEvent(name=self._task.name, id=self._task.id, obsnum=self._obsnum))
298
302
  self._task = None
303
+ self._project_priority = None
304
+ self._observation = None
299
305
  self._task_target = None
300
306
  self._obsnum = None
301
307
  self._task_start = None
@@ -312,6 +318,8 @@ class Mastermind(Module, IAutonomous, IRobotic, IFitsHeaderBefore):
312
318
  # finish
313
319
  log.info("Finished task %s.", self._task.name)
314
320
  self._task = None
321
+ self._project_priority = None
322
+ self._observation = None
315
323
  self._task_target = None
316
324
  self._obsnum = None
317
325
  self._task_start = None
@@ -321,6 +329,18 @@ class Mastermind(Module, IAutonomous, IRobotic, IFitsHeaderBefore):
321
329
  # sleep?
322
330
  await asyncio.sleep(self._after_task_sleep)
323
331
 
332
+ async def _lookup_project_priority(self, task: Task) -> float | None:
333
+ """Priority of the project of the given task, or None if it cannot be determined (no task archive, an
334
+ archive without projects, unknown project, project without priority). Looked up once when a task starts,
335
+ so that all frames of a run carry the same value."""
336
+ if self._task_archive is None or not task.project:
337
+ return None
338
+ try:
339
+ projects = await self._task_archive.get_projects()
340
+ except NotImplementedError:
341
+ return None
342
+ return next((p.priority for p in projects if p.code == task.project), None)
343
+
324
344
  async def get_fits_header_before(
325
345
  self, namespaces: list[str] | None = None, **kwargs: Any
326
346
  ) -> dict[str, FitsHeaderEntry]:
@@ -342,6 +362,24 @@ class Mastermind(Module, IAutonomous, IRobotic, IFitsHeaderBefore):
342
362
  hdr["PROJECT"] = FitsHeaderEntry(self._task.project, "Project code")
343
363
  if self._obsnum is not None:
344
364
  hdr["OBSNUM"] = FitsHeaderEntry(self._obsnum, "Observation number (night-obsnum)")
365
+
366
+ # what the frame was asked for: priorities and constraints of the task as it was when the run started
367
+ if self._task.priority is not None:
368
+ hdr["HIERARCH REQ PRIORITY"] = FitsHeaderEntry(float(self._task.priority), "")
369
+ if self._project_priority is not None:
370
+ hdr["HIERARCH REQ PROJECT PRIORITY"] = FitsHeaderEntry(float(self._project_priority), "")
371
+ if self._observation is not None and self._observation.priority is not None:
372
+ # the value the scheduler ranked this slot by when it planned it (merit times task priority times
373
+ # project priority), only set by schedulers that fill Observation.priority
374
+ hdr["HIERARCH REQ MERIT"] = FitsHeaderEntry(float(self._observation.priority), "plan-time rank")
375
+ counts: dict[type, int] = {}
376
+ for constraint in self._task.constraints:
377
+ counts[type(constraint)] = counts.get(type(constraint), 0) + 1
378
+ for key, entry in constraint.get_fits_headers(counts[type(constraint)]).items():
379
+ if key in hdr:
380
+ log.debug("Header %s already set, ignoring the one of %s.", key, type(constraint).__name__)
381
+ continue
382
+ hdr[key] = entry
345
383
  hdr.update(version_fits_headers(self.name))
346
384
  return hdr
347
385
  else:
@@ -230,7 +230,7 @@ class Scheduler(Module, IRunnable, IRoboticScheduler):
230
230
  log.info("Removed: %d, added: %d, changed: %d", len(removed), len(added), len(changed))
231
231
  if len(removed) == 1:
232
232
  log.info(
233
- "Found 1 removed block with ID %d. Last task ID was %s, current is %s.",
233
+ "Found 1 removed block with ID %s. Last task ID was %s, current is %s.",
234
234
  removed[0],
235
235
  str(self._last_task_id),
236
236
  str(self._current_task_id),
@@ -79,9 +79,10 @@ class MissingObserverError(exc.MotionError):
79
79
  pass
80
80
 
81
81
 
82
- class AltitudeLimitError(exc.PyobsError):
82
+ class AltitudeLimitError(exc.PointingLimitError):
83
83
  """Destination altitude is below the configured limit -- an expected, deferrable condition
84
- from a scheduler's point of view, not a failure to alert on. Deliberately not a MotionError:
84
+ from a scheduler's point of view, not a failure to alert on. Derives from PointingLimitError, so a caller
85
+ that never imported this module still catches it as that. Deliberately not a MotionError:
85
86
  it's not a motion fault (nothing was attempted), so it must not count towards the module's
86
87
  repeated-motion-failure escalation (see _register_exception(exc.MotionError, ...) below) --
87
88
  picking a target below the horizon is normal, not a sign the telescope is broken."""
@@ -20,7 +20,7 @@ from pyobs.utils.versions import version_fits_headers
20
20
  log = logging.getLogger(__name__)
21
21
 
22
22
 
23
- class WeatherResponseError(exc.PyobsError):
23
+ class WeatherResponseError(exc.GeneralError):
24
24
  """The weather station's API response was malformed or incomplete (missing an expected field)
25
25
  -- plausibly transient (a flaky station/network), worth retrying, as opposed to a caller
26
26
  passing a bad station/sensor name."""
@@ -45,20 +45,27 @@ class Observation(BaseModel):
45
45
  consumes it -- it is always None for the pending/in_progress states Mastermind schedules
46
46
  on, and is declared so the portal's payloads keep validating under ``extra="forbid"``."""
47
47
 
48
+ @property
49
+ def task_id(self) -> Any:
50
+ """Id of the task: ``task.id`` for a full ``Task``, else ``task`` itself (the portal serializes
51
+ it as a plain id)."""
52
+ return self.task.id if isinstance(self.task, Task) else self.task
53
+
48
54
  def __str__(self) -> str:
49
- return (
50
- f"Observation {self.id} of {self.task.name} (#{self.task.id}) "
51
- f"from {self.start} to {self.end} [{self.state}]"
52
- )
55
+ if isinstance(self.task, Task):
56
+ task = f"{self.task.name} (#{self.task.id})"
57
+ else:
58
+ task = f"#{self.task}"
59
+ return f"Observation {self.id} of {task} from {self.start} to {self.end} [{self.state}]"
53
60
 
54
61
  def __eq__(self, other: object) -> bool:
55
62
  if isinstance(other, Observation):
56
- return bool(self.task.id == other.task.id and self.start == other.start and self.end == other.end)
63
+ return bool(self.task_id == other.task_id and self.start == other.start and self.end == other.end)
57
64
  return super().__eq__(other)
58
65
 
59
66
  def __ne__(self, other: object) -> bool:
60
67
  if isinstance(other, Observation):
61
- return bool(self.task.id != other.task.id or self.start != other.start or self.end != other.end)
68
+ return bool(self.task_id != other.task_id or self.start != other.start or self.end != other.end)
62
69
  return super().__ne__(other)
63
70
 
64
71
  def __lt__(self, other: object) -> bool:
@@ -107,7 +114,7 @@ class ObservationList(UserList[Observation]): # noqa: F821
107
114
  def filter(
108
115
  self,
109
116
  state: ObservationState | None = None,
110
- task_id: int | None = None,
117
+ task_id: Any | None = None,
111
118
  start_before: Time | None = None,
112
119
  start_after: Time | None = None,
113
120
  end_before: Time | None = None,
@@ -117,7 +124,7 @@ class ObservationList(UserList[Observation]): # noqa: F821
117
124
  if state is not None:
118
125
  new_list = [obs for obs in new_list if obs.state == state]
119
126
  if task_id is not None:
120
- new_list = [obs for obs in new_list if obs.task.id == task_id]
127
+ new_list = [obs for obs in new_list if obs.task_id == task_id]
121
128
  if start_before is not None:
122
129
  new_list = [obs for obs in new_list if obs.start <= start_before]
123
130
  if start_after is not None:
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
3
+ from typing import TYPE_CHECKING, ClassVar
4
4
 
5
5
  import astroplan
6
6
  import numpy as np
@@ -22,6 +22,10 @@ class AirmassConstraint(Constraint):
22
22
  cost: float = 2.0
23
23
  target_dependent: bool = True
24
24
  max_airmass: float = Field(ge=1.0, le=9.9, default=1.3)
25
+ req_token: ClassVar[str | None] = "AIRMASS"
26
+
27
+ def req_fields(self) -> dict[str, tuple[int | float | str, str]]:
28
+ return {"MAX": (float(self.max_airmass), "")}
25
29
 
26
30
  def to_astroplan(self) -> astroplan.AirmassConstraint:
27
31
  return astroplan.AirmassConstraint(max=self.max_airmass)