pyobs-core 2.9.0__tar.gz → 2.9.2__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 (460) hide show
  1. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/PKG-INFO +1 -1
  2. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/image/imagewatcher.py +149 -80
  3. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/utils/pushnotifier.py +8 -1
  4. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyproject.toml +1 -1
  5. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/.gitignore +0 -0
  6. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/LICENSE +0 -0
  7. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/__init__.py +0 -0
  8. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/application.py +0 -0
  9. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/background_task.py +0 -0
  10. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/cli/__init__.py +0 -0
  11. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/cli/_cli.py +0 -0
  12. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/cli/pyobs.py +0 -0
  13. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/cli/pyobsd.py +0 -0
  14. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/cli/pyobsw.py +0 -0
  15. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/__init__.py +0 -0
  16. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/comm.py +0 -0
  17. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/commlogging.py +0 -0
  18. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/dummy/__init__.py +0 -0
  19. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/dummy/dummycomm.py +0 -0
  20. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/local/__init__.py +0 -0
  21. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/local/localcomm.py +0 -0
  22. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/local/localnetwork.py +0 -0
  23. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/proxy.py +0 -0
  24. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/xmpp/__init__.py +0 -0
  25. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/xmpp/rpc.py +0 -0
  26. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/xmpp/serializer.py +0 -0
  27. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/xmpp/xep_0009/__init__.py +0 -0
  28. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/xmpp/xep_0009/binding.py +0 -0
  29. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/xmpp/xep_0009/rpc.py +0 -0
  30. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/xmpp/xep_0009_timeout/__init__.py +0 -0
  31. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/xmpp/xep_0009_timeout/rpc.py +0 -0
  32. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/xmpp/xep_0009_timeout/stanza/RPC.py +0 -0
  33. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/xmpp/xep_0009_timeout/stanza/__init__.py +0 -0
  34. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/xmpp/xmppclient.py +0 -0
  35. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/comm/xmpp/xmppcomm.py +0 -0
  36. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/__init__.py +0 -0
  37. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/badweather.py +0 -0
  38. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/event.py +0 -0
  39. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/exposurestatuschanged.py +0 -0
  40. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/filterchanged.py +0 -0
  41. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/focusfound.py +0 -0
  42. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/goodweather.py +0 -0
  43. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/log.py +0 -0
  44. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/modechanged.py +0 -0
  45. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/moduleclosed.py +0 -0
  46. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/moduleopened.py +0 -0
  47. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/motionstatuschanged.py +0 -0
  48. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/move.py +0 -0
  49. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/newimage.py +0 -0
  50. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/newspectrum.py +0 -0
  51. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/offsets.py +0 -0
  52. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/roofclosing.py +0 -0
  53. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/roofopened.py +0 -0
  54. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/taskfailed.py +0 -0
  55. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/taskfinished.py +0 -0
  56. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/taskskipped.py +0 -0
  57. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/taskstarted.py +0 -0
  58. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/events/testevent.py +0 -0
  59. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/__init__.py +0 -0
  60. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/image.py +0 -0
  61. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/meta/__init__.py +0 -0
  62. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/meta/altazoffsets.py +0 -0
  63. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/meta/exptime.py +0 -0
  64. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/meta/genericoffset.py +0 -0
  65. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/meta/onskydistance.py +0 -0
  66. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/meta/pixeloffsets.py +0 -0
  67. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/meta/radecoffsets.py +0 -0
  68. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/meta/skyoffsets.py +0 -0
  69. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processor.py +0 -0
  70. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/__init__.py +0 -0
  71. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/_daobackgroundremover.py +0 -0
  72. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/annotation/__init__.py +0 -0
  73. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/annotation/_pillow.py +0 -0
  74. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/annotation/circle.py +0 -0
  75. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/annotation/crosshair.py +0 -0
  76. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/annotation/text.py +0 -0
  77. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/astrometry/__init__.py +0 -0
  78. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/astrometry/_dotnet_request.py +0 -0
  79. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/astrometry/_dotnet_request_builder.py +0 -0
  80. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/astrometry/_dotnet_request_logger.py +0 -0
  81. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/astrometry/_dotnet_response_saver.py +0 -0
  82. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/astrometry/astrometry.py +0 -0
  83. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/astrometry/dotnet.py +0 -0
  84. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/calibration/__init__.py +0 -0
  85. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/calibration/_calibration_cache.py +0 -0
  86. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/calibration/_ccddata_calibrator.py +0 -0
  87. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/calibration/calibration.py +0 -0
  88. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/detection/__init__.py +0 -0
  89. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/detection/_pysep_stats_calculator.py +0 -0
  90. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/detection/_source_catalog.py +0 -0
  91. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/detection/daophot.py +0 -0
  92. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/detection/pysep.py +0 -0
  93. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/detection/simpledisk.py +0 -0
  94. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/detection/sourcedetection.py +0 -0
  95. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/exptime/__init__.py +0 -0
  96. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/exptime/exptime.py +0 -0
  97. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/exptime/star.py +0 -0
  98. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/image/__init__.py +0 -0
  99. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/image/addfitsheaders.py +0 -0
  100. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/image/download.py +0 -0
  101. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/image/flip.py +0 -0
  102. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/image/grayscale.py +0 -0
  103. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/image/httpserver.py +0 -0
  104. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/image/normalize.py +0 -0
  105. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/image/save.py +0 -0
  106. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/image/saveimage.py +0 -0
  107. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/image/smooth.py +0 -0
  108. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/image/softbin.py +0 -0
  109. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/misc/__init__.py +0 -0
  110. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/misc/addmask.py +0 -0
  111. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/misc/catalogcircularmask.py +0 -0
  112. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/misc/circularmask.py +0 -0
  113. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/misc/createfilename.py +0 -0
  114. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/misc/imagesourcefilter.py +0 -0
  115. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/misc/removebackground.py +0 -0
  116. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/modules/__init__.py +0 -0
  117. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/modules/getfitsheaders.py +0 -0
  118. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/offsets/__init__.py +0 -0
  119. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/offsets/astrometry.py +0 -0
  120. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/offsets/brighteststar.py +0 -0
  121. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/offsets/brighteststar_guiding.py +0 -0
  122. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/offsets/dummyoffsets.py +0 -0
  123. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/offsets/dummyskyoffsets.py +0 -0
  124. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/offsets/fitsheader.py +0 -0
  125. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/offsets/offsets.py +0 -0
  126. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/offsets/pixeloffsets.py +0 -0
  127. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/offsets/projected.py +0 -0
  128. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/offsets/spilled_light.py +0 -0
  129. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/photometry/__init__.py +0 -0
  130. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/photometry/_photometry_calculator.py +0 -0
  131. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/photometry/_photutil_aperture_photometry.py +0 -0
  132. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/photometry/_sep_aperture_photometry.py +0 -0
  133. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/photometry/aperture_photometry.py +0 -0
  134. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/photometry/photometry.py +0 -0
  135. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/photometry/photutil.py +0 -0
  136. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/photometry/pysep.py +0 -0
  137. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/wcs/__init__.py +0 -0
  138. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/images/processors/wcs/solarhelioprojective.py +0 -0
  139. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IAbortable.py +0 -0
  140. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IAcquisition.py +0 -0
  141. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IAutoFocus.py +0 -0
  142. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IAutoGuiding.py +0 -0
  143. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IAutonomous.py +0 -0
  144. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IBinning.py +0 -0
  145. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/ICalibrate.py +0 -0
  146. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/ICamera.py +0 -0
  147. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IConfig.py +0 -0
  148. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/ICooling.py +0 -0
  149. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IData.py +0 -0
  150. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IDataSequence.py +0 -0
  151. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IDome.py +0 -0
  152. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IExposure.py +0 -0
  153. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IExposureTime.py +0 -0
  154. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IFilters.py +0 -0
  155. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IFitsHeaderAfter.py +0 -0
  156. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IFitsHeaderBefore.py +0 -0
  157. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IFlatField.py +0 -0
  158. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IFocusModel.py +0 -0
  159. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IFocuser.py +0 -0
  160. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IGain.py +0 -0
  161. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IImageFormat.py +0 -0
  162. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IImageType.py +0 -0
  163. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IMode.py +0 -0
  164. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IModule.py +0 -0
  165. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IMotion.py +0 -0
  166. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IMultiFiber.py +0 -0
  167. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IOffsetsAltAz.py +0 -0
  168. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IOffsetsRaDec.py +0 -0
  169. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IPointingAltAz.py +0 -0
  170. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IPointingBody.py +0 -0
  171. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IPointingHeliocentricPolar.py +0 -0
  172. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IPointingHeliographicStonyhurst.py +0 -0
  173. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IPointingHelioprojective.py +0 -0
  174. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IPointingOrbitalElements.py +0 -0
  175. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IPointingRaDec.py +0 -0
  176. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IPointingSeries.py +0 -0
  177. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IPushNotifications.py +0 -0
  178. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IReady.py +0 -0
  179. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IRobotic.py +0 -0
  180. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IRoboticScheduler.py +0 -0
  181. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IRoof.py +0 -0
  182. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IRotation.py +0 -0
  183. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IRunnable.py +0 -0
  184. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IRunning.py +0 -0
  185. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IScriptRunner.py +0 -0
  186. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/ISpectrograph.py +0 -0
  187. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IStartStop.py +0 -0
  188. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IStructuredConfig.py +0 -0
  189. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/ISyncTarget.py +0 -0
  190. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/ITelescope.py +0 -0
  191. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/ITemperatures.py +0 -0
  192. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/ITrackingMode.py +0 -0
  193. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/ITrackingRate.py +0 -0
  194. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IVideo.py +0 -0
  195. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IWeather.py +0 -0
  196. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/IWindow.py +0 -0
  197. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/__init__.py +0 -0
  198. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/interfaces/interface.py +0 -0
  199. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/mixins/__init__.py +0 -0
  200. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/mixins/camerasettings.py +0 -0
  201. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/mixins/fitsheader.py +0 -0
  202. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/mixins/fitsnamespace.py +0 -0
  203. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/mixins/follow.py +0 -0
  204. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/mixins/motionstatus.py +0 -0
  205. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/mixins/pipeline.py +0 -0
  206. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/mixins/waitformotion.py +0 -0
  207. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/mixins/weatheraware.py +0 -0
  208. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/__init__.py +0 -0
  209. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/camera/__init__.py +0 -0
  210. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/camera/adaptive.py +0 -0
  211. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/camera/basecamera.py +0 -0
  212. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/camera/basespectrograph.py +0 -0
  213. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/camera/basevideo.py +0 -0
  214. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/camera/dummycamera.py +0 -0
  215. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/camera/dummyspectrograph.py +0 -0
  216. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/camera/dummyvideo.py +0 -0
  217. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/camera/pipelinecamera.py +0 -0
  218. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/flatfield/__init__.py +0 -0
  219. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/flatfield/flatfield.py +0 -0
  220. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/flatfield/pointing.py +0 -0
  221. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/flatfield/scheduler.py +0 -0
  222. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/focus/__init__.py +0 -0
  223. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/focus/dummyautofocus.py +0 -0
  224. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/focus/focusmodel.py +0 -0
  225. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/focus/focusseries.py +0 -0
  226. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/image/__init__.py +0 -0
  227. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/image/imagewriter.py +0 -0
  228. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/image/pipeline.py +0 -0
  229. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/image/seeing.py +0 -0
  230. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/module.py +0 -0
  231. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/pointing/__init__.py +0 -0
  232. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/pointing/_base.py +0 -0
  233. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/pointing/_baseguiding.py +0 -0
  234. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/pointing/acquisition.py +0 -0
  235. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/pointing/autoguiding.py +0 -0
  236. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/pointing/dummyacquisition.py +0 -0
  237. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/pointing/dummyguiding.py +0 -0
  238. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/pointing/guidingstatistics/__init__.py +0 -0
  239. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/pointing/guidingstatistics/guidingstatistics.py +0 -0
  240. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/pointing/guidingstatistics/pixeloffset.py +0 -0
  241. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/pointing/guidingstatistics/skyoffsets.py +0 -0
  242. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/pointing/guidingstatistics/uptime.py +0 -0
  243. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/pointing/scienceframeguiding.py +0 -0
  244. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/robotic/__init__.py +0 -0
  245. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/robotic/dummymastermind.py +0 -0
  246. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/robotic/dummyscheduler.py +0 -0
  247. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/robotic/mastermind.py +0 -0
  248. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/robotic/pointing.py +0 -0
  249. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/robotic/scheduler.py +0 -0
  250. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/robotic/scriptrunner.py +0 -0
  251. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/roof/__init__.py +0 -0
  252. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/roof/basedome.py +0 -0
  253. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/roof/baseroof.py +0 -0
  254. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/roof/dummyroof.py +0 -0
  255. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/telescope/__init__.py +0 -0
  256. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/telescope/_dummytelescopebase.py +0 -0
  257. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/telescope/basetelescope.py +0 -0
  258. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/telescope/dummyaltaztelescope.py +0 -0
  259. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/telescope/dummyradectelescope.py +0 -0
  260. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/telescope/dummysolartelescope.py +0 -0
  261. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/test/__init__.py +0 -0
  262. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/test/standalone.py +0 -0
  263. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/utils/__init__.py +0 -0
  264. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/utils/dummymode.py +0 -0
  265. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/utils/dummystructuredconfig.py +0 -0
  266. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/utils/fluentlogger.py +0 -0
  267. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/utils/httpfilecache.py +0 -0
  268. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/utils/kiosk.py +0 -0
  269. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/utils/matrix.py +0 -0
  270. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/utils/stellarium.py +0 -0
  271. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/utils/telegram.py +0 -0
  272. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/utils/trigger.py +0 -0
  273. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/weather/__init__.py +0 -0
  274. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/weather/mockweather.py +0 -0
  275. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/weather/weather.py +0 -0
  276. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/weather/weather_api.py +0 -0
  277. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/modules/weather/weather_state.py +0 -0
  278. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/object.py +0 -0
  279. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/py.typed +0 -0
  280. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/__init__.py +0 -0
  281. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/instruments.py +0 -0
  282. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/observation.py +0 -0
  283. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/__init__.py +0 -0
  284. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/_executor.py +0 -0
  285. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/astroplanscheduler.py +0 -0
  286. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/constraints/__init__.py +0 -0
  287. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/constraints/airmassconstraint.py +0 -0
  288. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/constraints/constraint.py +0 -0
  289. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/constraints/moonilluminationconstraint.py +0 -0
  290. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/constraints/moonseparationconstraint.py +0 -0
  291. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/constraints/solarelevationconstraint.py +0 -0
  292. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/constraints/timeconstraint.py +0 -0
  293. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/dataprovider.py +0 -0
  294. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/merits/__init__.py +0 -0
  295. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/merits/aftertime.py +0 -0
  296. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/merits/beforetime.py +0 -0
  297. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/merits/constant.py +0 -0
  298. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/merits/follow.py +0 -0
  299. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/merits/interval.py +0 -0
  300. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/merits/merit.py +0 -0
  301. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/merits/pernight.py +0 -0
  302. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/merits/random.py +0 -0
  303. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/merits/timewindow.py +0 -0
  304. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/merits/transit.py +0 -0
  305. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/observationarchiveevolution.py +0 -0
  306. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/ondemandscheduler.py +0 -0
  307. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/targets/__init__.py +0 -0
  308. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/targets/dynamictarget.py +0 -0
  309. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/targets/heliocentricpolartarget.py +0 -0
  310. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/targets/helioprojectivetarget.py +0 -0
  311. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/targets/picker/__init__.py +0 -0
  312. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/targets/picker/csvpicker.py +0 -0
  313. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/targets/picker/picker.py +0 -0
  314. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/targets/siderealtarget.py +0 -0
  315. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/targets/target.py +0 -0
  316. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scheduler/taskscheduler.py +0 -0
  317. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/__init__.py +0 -0
  318. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/calibration/__init__.py +0 -0
  319. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/calibration/darkbias.py +0 -0
  320. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/calibration/pointing.py +0 -0
  321. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/calibration/skyflats.py +0 -0
  322. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/control/__init__.py +0 -0
  323. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/control/cases.py +0 -0
  324. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/control/conditional.py +0 -0
  325. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/control/parallel.py +0 -0
  326. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/control/selector.py +0 -0
  327. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/control/sequential.py +0 -0
  328. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/exceptions.py +0 -0
  329. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/imaging/__init__.py +0 -0
  330. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/imaging/autofocus.py +0 -0
  331. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/imaging/imaging.py +0 -0
  332. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/imaging/transitimaging.py +0 -0
  333. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/script.py +0 -0
  334. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/utils/__init__.py +0 -0
  335. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/utils/callmodule.py +0 -0
  336. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/utils/debugtrigger.py +0 -0
  337. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/scripts/utils/log.py +0 -0
  338. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/__init__.py +0 -0
  339. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/filesystem/__init__.py +0 -0
  340. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/filesystem/observationarchive.py +0 -0
  341. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/filesystem/taskarchive.py +0 -0
  342. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/lco/__init__.py +0 -0
  343. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/lco/_portal.py +0 -0
  344. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/lco/_schedulereader.py +0 -0
  345. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/lco/_schedulewriter.py +0 -0
  346. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/lco/configdb.py +0 -0
  347. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/lco/mockobservationarchive.py +0 -0
  348. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/lco/observationarchive.py +0 -0
  349. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/lco/scripts/__init__.py +0 -0
  350. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/lco/scripts/autofocus.py +0 -0
  351. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/lco/scripts/default.py +0 -0
  352. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/lco/scripts/script.py +0 -0
  353. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/lco/task.py +0 -0
  354. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/lco/taskarchive.py +0 -0
  355. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/lco/taskrunner.py +0 -0
  356. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/memory/__init__.py +0 -0
  357. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/memory/observationarchive.py +0 -0
  358. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/memory/taskarchive.py +0 -0
  359. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/observationarchive.py +0 -0
  360. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/portal/__init__.py +0 -0
  361. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/portal/observationarchive.py +0 -0
  362. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/portal/taskarchive.py +0 -0
  363. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/storage/taskarchive.py +0 -0
  364. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/task.py +0 -0
  365. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/taskrunner.py +0 -0
  366. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/__init__.py +0 -0
  367. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/archive/__init__.py +0 -0
  368. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/archive/archive.py +0 -0
  369. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/archive/local_archive.py +0 -0
  370. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/archive/pyobs_archive.py +0 -0
  371. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/calibration.py +0 -0
  372. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/exptime/__init__.py +0 -0
  373. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/exptime/exptime.py +0 -0
  374. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/exptime/stellarexptime.py +0 -0
  375. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/skyflats/__init__.py +0 -0
  376. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/skyflats/exptimeeval.py +0 -0
  377. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/skyflats/flatfielder.py +0 -0
  378. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/skyflats/pointing/__init__.py +0 -0
  379. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/skyflats/pointing/base.py +0 -0
  380. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/skyflats/pointing/static.py +0 -0
  381. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/skyflats/priorities/__init__.py +0 -0
  382. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/skyflats/priorities/archive.py +0 -0
  383. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/skyflats/priorities/base.py +0 -0
  384. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/skyflats/priorities/const.py +0 -0
  385. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/robotic/utils/skyflats/scheduler.py +0 -0
  386. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/__init__.py +0 -0
  387. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/average.py +0 -0
  388. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/cache.py +0 -0
  389. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/config.py +0 -0
  390. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/config_schema.py +0 -0
  391. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/coordinates.py +0 -0
  392. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/curvefit.py +0 -0
  393. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/enums.py +0 -0
  394. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/exceptions.py +0 -0
  395. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/exptime_grouping.py +0 -0
  396. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/fits.py +0 -0
  397. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/focusseries/__init__.py +0 -0
  398. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/focusseries/base.py +0 -0
  399. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/focusseries/photometry.py +0 -0
  400. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/focusseries/projection.py +0 -0
  401. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/grids/__init__.py +0 -0
  402. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/grids/filters.py +0 -0
  403. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/grids/grid.py +0 -0
  404. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/grids/gridnode.py +0 -0
  405. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/grids/pipeline.py +0 -0
  406. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/gui/__init__.py +0 -0
  407. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/gui/camera/__init__.py +0 -0
  408. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/gui/camera/binningwidget.py +0 -0
  409. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/gui/camera/datadisplaywidget.py +0 -0
  410. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/gui/camera/exposewidget.py +0 -0
  411. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/gui/camera/exposuretimewidget.py +0 -0
  412. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/gui/camera/imageformatwidget.py +0 -0
  413. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/gui/camera/listpickerdialog.py +0 -0
  414. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/gui/camera/windowingwidget.py +0 -0
  415. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/http.py +0 -0
  416. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/influxdb.py +0 -0
  417. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/logger.py +0 -0
  418. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/logging/__init__.py +0 -0
  419. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/logging/context.py +0 -0
  420. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/logging/resolvableerror.py +0 -0
  421. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/modulegui.py +0 -0
  422. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/offsets/__init__.py +0 -0
  423. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/offsets/applyaltazoffsets.py +0 -0
  424. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/offsets/applyoffsets.py +0 -0
  425. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/offsets/applyradecoffsets.py +0 -0
  426. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/parallel.py +0 -0
  427. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/pipeline/__init__.py +0 -0
  428. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/pipeline/pipeline.py +0 -0
  429. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/pipeline/progress.py +0 -0
  430. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/pipeline/reduction.py +0 -0
  431. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/pipeline/reduction_base.py +0 -0
  432. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/publisher/__init__.py +0 -0
  433. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/publisher/csv.py +0 -0
  434. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/publisher/http.py +0 -0
  435. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/publisher/log.py +0 -0
  436. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/publisher/multi.py +0 -0
  437. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/publisher/publisher.py +0 -0
  438. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/serialization.py +0 -0
  439. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/shellcommand.py +0 -0
  440. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/threads/__init__.py +0 -0
  441. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/threads/lockwithabort.py +0 -0
  442. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/time.py +0 -0
  443. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/units.py +0 -0
  444. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/utils/versions.py +0 -0
  445. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/version.py +0 -0
  446. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/vfs/__init__.py +0 -0
  447. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/vfs/archivefile.py +0 -0
  448. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/vfs/bufferedfile.py +0 -0
  449. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/vfs/file.py +0 -0
  450. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/vfs/filelists/__init__.py +0 -0
  451. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/vfs/filelists/filelist.py +0 -0
  452. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/vfs/filelists/testing.py +0 -0
  453. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/vfs/httpfile.py +0 -0
  454. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/vfs/localfile.py +0 -0
  455. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/vfs/memfile.py +0 -0
  456. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/vfs/sftpfile.py +0 -0
  457. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/vfs/smbfile.py +0 -0
  458. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/vfs/sshfile.py +0 -0
  459. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/vfs/tempfile.py +0 -0
  460. {pyobs_core-2.9.0 → pyobs_core-2.9.2}/pyobs/vfs/vfs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: pyobs-core
3
- Version: 2.9.0
3
+ Version: 2.9.2
4
4
  Summary: robotic telescope software
5
5
  Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ import logging
4
4
  import os
5
5
  import time
6
6
  import warnings
7
- from dataclasses import dataclass
7
+ from dataclasses import dataclass, field
8
8
  from pathlib import Path, PurePosixPath
9
9
  from typing import Any
10
10
 
@@ -30,6 +30,21 @@ class CurrentFile:
30
30
  hdu_list: fits.HDUList | None = None
31
31
 
32
32
 
33
+ @dataclass
34
+ class _RetryState:
35
+ attempts: int = 0
36
+ first_failed_at: float = field(default_factory=time.time)
37
+ alerted: bool = False
38
+
39
+
40
+ # Exceptions treated as unrecoverable regardless of which phase (read or write) raises them: no
41
+ # amount of retrying fixes a deleted file or wrong permissions. Everything else is assumed
42
+ # possibly transient (a down destination, a network hiccup) and gets bounded-backoff-forever
43
+ # retries instead. Deliberately coarse and biased towards over-retrying rather than silently
44
+ # dropping a file we misclassified -- see the design doc for the reasoning.
45
+ _TERMINAL_EXCEPTIONS = (FileNotFoundError, PermissionError, IsADirectoryError)
46
+
47
+
33
48
  class ImageWatcher(Module):
34
49
  """Watch for new files and write them to all given destinations.
35
50
 
@@ -39,8 +54,11 @@ class ImageWatcher(Module):
39
54
  New files are not processed immediately, but only after a wait time (``wait_time``) has passed since they were
40
55
  detected. This is mainly to make sure the file is fully written before it is read: while the inotify watcher
41
56
  only reports files after they have been closed for writing (``CLOSE_WRITE``), the polling watcher and the
42
- initial scan in ``open()`` can see a file while its write is still in progress. The same wait time is also
43
- used to delay re-processing of files whose destination copy failed (see ``_worker``).
57
+ initial scan in ``open()`` can see a file while its write is still in progress.
58
+
59
+ A file that fails to process is retried with exponentially growing backoff, up to ``backoff_cap``, unless the
60
+ failure is unrecoverable (see ``_handle_failure``), in which case it's left in place and never retried. See
61
+ the ``error_after``/``backoff_cap`` constructor args for details.
44
62
  """
45
63
 
46
64
  __module__ = "pyobs.modules.image"
@@ -54,6 +72,9 @@ class ImageWatcher(Module):
54
72
  wait_time: int = 10,
55
73
  pattern: str = "*",
56
74
  flatten: bool = True,
75
+ *,
76
+ error_after: int,
77
+ backoff_cap: int = 3600,
57
78
  **kwargs: Any,
58
79
  ):
59
80
  """Create a new image watcher.
@@ -64,8 +85,8 @@ class ImageWatcher(Module):
64
85
  poll: If True, watchpath is polled instead of watched by inotify.
65
86
  poll_interval: Interval for polling in seconds, if poll is True.
66
87
  wait_time: Time in seconds between adding a file to the list and processing it. Gives a file that is
67
- still being written time to finish (relevant for poll mode and the initial scan) and spaces out
68
- re-queued files after failed destination copies.
88
+ still being written time to finish (relevant for poll mode and the initial scan) and is also the
89
+ base interval for retrying a file after a transient failure (see ``error_after``).
69
90
  pattern: Only watch/process files matching this fnmatch pattern, checked against the full path (so
70
91
  e.g. "*.fits" also matches files found in subdirectories). This is the single point of filtering,
71
92
  applied uniformly in ``add_file`` regardless of watch mode -- inotify events, polling, and the
@@ -79,6 +100,21 @@ class ImageWatcher(Module):
79
100
  write (the default for e.g. ``LocalFile``) -- a destination configured to not do that would
80
101
  repeatedly fail and re-queue any file whose relative path needs a parent directory that doesn't
81
102
  exist yet.
103
+ error_after: Seconds a file may keep failing with a transient error (see below) before a single
104
+ ``ERROR`` is logged for it. Required -- no sane default exists, since how long a destination can
105
+ legitimately be unreachable (a remote archive, an NFS mount, ...) varies per deployment. Set it
106
+ to how long an outage has to last before it's worth paging someone. The file keeps being retried
107
+ (with growing backoff, capped at ``backoff_cap``) indefinitely either way; this only controls
108
+ when the one-time alert fires, not whether retrying continues.
109
+ backoff_cap: Maximum seconds between retries of a file that keeps failing transiently. The interval
110
+ starts at ``wait_time`` and doubles on each failure up to this cap.
111
+
112
+ Note:
113
+ A failure is either transient (retried forever, with the backoff/``error_after`` behavior above) or
114
+ terminal (``FileNotFoundError``, ``PermissionError``, ``IsADirectoryError``, wherever in the pipeline
115
+ they're raised) -- a terminal failure is logged as an ``ERROR`` immediately and the file is never
116
+ re-queued; it's simply left where it is in ``watchpath``; neither watch mode re-detects an existing,
117
+ unchanged file, so it stays visibly stuck rather than being retried forever or silently vanishing.
82
118
 
83
119
  Note:
84
120
  If a directory holding a file that's already queued (added but not yet processed by ``_worker``) is
@@ -105,6 +141,9 @@ class ImageWatcher(Module):
105
141
  self._wait_time = wait_time
106
142
  self._pattern = pattern
107
143
  self._flatten = flatten
144
+ self._error_after = error_after
145
+ self._backoff_cap = backoff_cap
146
+ self._retry: dict[str, _RetryState] = {}
108
147
  self.current_file: CurrentFile | None = None
109
148
 
110
149
  # filename patterns
@@ -203,84 +242,114 @@ class ImageWatcher(Module):
203
242
  await asyncio.sleep(wait)
204
243
  log.info("Working on file %s...", filename)
205
244
 
206
- # better safe than sorry
207
245
  try:
208
- # get file data
209
- async with self.vfs.open_file(filename, "rb") as fd:
210
- data = await fd.read()
211
-
212
- # only attempt to load the file as FITS when its name suggests it is one; any
213
- # other file (e.g. a raw camera binary like "w123.0") is still copied as-is to
214
- # non-templated destinations, just without the parse attempt and the astropy
215
- # header warnings it raises on non-FITS content. A matching name is no
216
- # guarantee, though -- the data may be corrupt or truncated -- hence the
217
- # try/except below.
218
- fits_file = None
219
- if os.path.basename(filename).lower().endswith(FITS_FILENAME_SUFFIXES):
220
- try:
221
- with warnings.catch_warnings():
222
- warnings.simplefilter("ignore", fits.verify.VerifyWarning)
223
- fits_file = await asyncio.to_thread(fits.HDUList.fromstring, data)
224
- except Exception:
225
- fits_file = None
226
-
227
- # fill current file
228
- self.current_file = CurrentFile(filename=filename, data=data, hdu_list=fits_file)
229
-
230
- # loop archive and upload
231
- success = True
232
- for pattern in self._destinations:
233
- # if it contains {placeholders}, we assume it's a FITS file and format filename
234
- if "{" in pattern and "}" in pattern and fits_file is not None:
235
- # format filename
236
- out_filename = format_filename(fits_file["SCI"].header, pattern)
237
- if out_filename is None:
238
- raise ValueError("Could not create name for file.")
239
-
240
- elif self._flatten:
241
- # no formatting, so just add filename to destination
242
- out_filename = os.path.join(pattern, os.path.basename(filename))
243
-
244
- else:
245
- # preserve the file's path relative to watchpath under the destination
246
- rel = PurePosixPath(filename).relative_to(self._watchpath)
247
- out_filename = str(PurePosixPath(pattern) / rel)
248
-
249
- # store it
250
- log.info("Storing file as %s...", out_filename)
251
- self.current_file.out_filename = out_filename
252
- try:
253
- async with self.vfs.open_file(out_filename, "wb") as fd:
254
- await fd.write(data)
255
- except Exception as e:
256
- log.warning("Error while copying file, skipping for now: %s", e)
257
- success = False
258
- break
259
-
260
- # do extra processing
261
- if not await self.process_extra(filename):
262
- success = False
263
- break
264
-
265
- # no success?
266
- if not success:
267
- # re-queue file and skip file for now
268
- self._queue.put_nowait((filename, time.time() + self._wait_time))
269
- continue
270
-
271
- # close and delete files
272
- log.info("Removing file from watch directory...")
273
- if not await self.vfs.remove(filename):
274
- log.warning("Could not delete %s.", filename)
275
- else:
276
- # clean up any now-empty parent directories the file left behind
277
- await self._cleanup_empty_parents(filename)
278
-
279
- # cleanup extra
280
- await self.cleanup_extra(filename)
246
+ await self._process_file(filename)
247
+ except Exception as e:
248
+ self._handle_failure(filename, e)
249
+ else:
250
+ self._retry.pop(filename, None)
281
251
 
252
+ async def _process_file(self, filename: str) -> None:
253
+ """Read, archive and clean up a single file. Raises on any failure short of cleanup.
254
+
255
+ Args:
256
+ filename: Local filename of the file to process.
257
+ """
258
+ # get file data
259
+ async with self.vfs.open_file(filename, "rb") as fd:
260
+ data = await fd.read()
261
+
262
+ # only attempt to load the file as FITS when its name suggests it is one; any
263
+ # other file (e.g. a raw camera binary like "w123.0") is still copied as-is to
264
+ # non-templated destinations, just without the parse attempt and the astropy
265
+ # header warnings it raises on non-FITS content. A matching name is no
266
+ # guarantee, though -- the data may be corrupt or truncated -- hence the
267
+ # try/except below.
268
+ fits_file = None
269
+ if os.path.basename(filename).lower().endswith(FITS_FILENAME_SUFFIXES):
270
+ try:
271
+ with warnings.catch_warnings():
272
+ warnings.simplefilter("ignore", fits.verify.VerifyWarning)
273
+ fits_file = await asyncio.to_thread(fits.HDUList.fromstring, data)
282
274
  except Exception:
283
- log.exception("Something went wrong.")
275
+ fits_file = None
276
+
277
+ # fill current file
278
+ self.current_file = CurrentFile(filename=filename, data=data, hdu_list=fits_file)
279
+
280
+ # loop archive and upload
281
+ for pattern in self._destinations:
282
+ # if it contains {placeholders}, we assume it's a FITS file and format filename
283
+ if "{" in pattern and "}" in pattern and fits_file is not None:
284
+ # format filename
285
+ out_filename = format_filename(fits_file["SCI"].header, pattern)
286
+ if out_filename is None:
287
+ raise ValueError("Could not create name for file.")
288
+
289
+ elif self._flatten:
290
+ # no formatting, so just add filename to destination
291
+ out_filename = os.path.join(pattern, os.path.basename(filename))
292
+
293
+ else:
294
+ # preserve the file's path relative to watchpath under the destination
295
+ rel = PurePosixPath(filename).relative_to(self._watchpath)
296
+ out_filename = str(PurePosixPath(pattern) / rel)
297
+
298
+ # store it
299
+ log.info("Storing file as %s...", out_filename)
300
+ self.current_file.out_filename = out_filename
301
+ async with self.vfs.open_file(out_filename, "wb") as fd:
302
+ await fd.write(data)
303
+
304
+ # do extra processing
305
+ if not await self.process_extra(filename):
306
+ raise RuntimeError(f"process_extra rejected {filename}.")
307
+
308
+ # close and delete files
309
+ log.info("Removing file from watch directory...")
310
+ if not await self.vfs.remove(filename):
311
+ log.warning("Could not delete %s.", filename)
312
+ else:
313
+ # clean up any now-empty parent directories the file left behind
314
+ await self._cleanup_empty_parents(filename)
315
+
316
+ # cleanup extra -- the file has already been removed from watchpath and copied to every
317
+ # destination by this point, so a failure here can't be fixed by re-running the pipeline
318
+ # (it would just re-read a now-deleted file); log it and move on instead of re-queueing.
319
+ try:
320
+ await self.cleanup_extra(filename)
321
+ except Exception:
322
+ log.error("cleanup_extra failed for %s (file already transferred, not retrying).", filename)
323
+
324
+ def _handle_failure(self, filename: str, exc: Exception) -> None:
325
+ """Handle a failure from ``_process_file``: re-queue transient failures with backoff, give
326
+ up (without re-queueing) on unrecoverable ones.
327
+
328
+ Args:
329
+ filename: The file that failed to process.
330
+ exc: The exception raised by ``_process_file``.
331
+ """
332
+ if isinstance(exc, _TERMINAL_EXCEPTIONS):
333
+ log.error("Giving up on %s, unrecoverable error: %s", filename, exc)
334
+ self._retry.pop(filename, None)
335
+ return
336
+
337
+ state = self._retry.setdefault(filename, _RetryState())
338
+ state.attempts += 1
339
+ elapsed = time.time() - state.first_failed_at
340
+ delay = min(self._wait_time * 2**state.attempts, self._backoff_cap)
341
+ if elapsed >= self._error_after and not state.alerted:
342
+ log.error(
343
+ "Still failing on %s after %.0fs of retries (attempt %d): %s",
344
+ filename,
345
+ elapsed,
346
+ state.attempts,
347
+ exc,
348
+ )
349
+ state.alerted = True
350
+ else:
351
+ log.warning("Retrying %s (attempt %d, next in %ds): %s", filename, state.attempts, delay, exc)
352
+ self._queue.put_nowait((filename, time.time() + delay))
284
353
 
285
354
  async def _cleanup_empty_parents(self, filename: str) -> None:
286
355
  """Remove now-empty parent directories left behind by a just-deleted file.
@@ -87,7 +87,14 @@ class PushNotifier(Module, IPushNotifications):
87
87
  if self._fcm_app is not None:
88
88
  import firebase_admin
89
89
 
90
- firebase_admin.delete_app(self._fcm_app)
90
+ # delete_app() -> messaging service close() calls asyncio.run() internally, which
91
+ # raises if called from within a running event loop (our own) -- run it in a thread.
92
+ try:
93
+ await asyncio.wait_for(
94
+ asyncio.to_thread(firebase_admin.delete_app, self._fcm_app), timeout=_FIREBASE_CALL_TIMEOUT
95
+ )
96
+ except Exception:
97
+ log.exception("Failed to cleanly shut down Firebase app.")
91
98
  self._fcm_app = None
92
99
 
93
100
  async def _init_firebase(self) -> None:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyobs-core"
3
- version = "2.9.0"
3
+ version = "2.9.2"
4
4
  description = "robotic telescope software"
5
5
  authors = [{ name = "Tim-Oliver Husser", email = "thusser@uni-goettingen.de" }]
6
6
  requires-python = ">=3.11"
File without changes
File without changes
File without changes
File without changes