pyobs-core 2.0.0.dev48__tar.gz → 2.0.0.dev50__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 (446) hide show
  1. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/PKG-INFO +2 -2
  2. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/application.py +130 -61
  3. pyobs_core-2.0.0.dev50/pyobs/comm/xmpp/__init__.py +3 -0
  4. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/xmpp/serializer.py +34 -3
  5. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/xmpp/xmppcomm.py +24 -4
  6. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyproject.toml +2 -2
  7. pyobs_core-2.0.0.dev48/pyobs/comm/xmpp/__init__.py +0 -3
  8. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/.gitignore +0 -0
  9. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/LICENSE +0 -0
  10. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/__init__.py +0 -0
  11. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/background_task.py +0 -0
  12. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/cli/__init__.py +0 -0
  13. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/cli/_cli.py +0 -0
  14. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/cli/pyobs.py +0 -0
  15. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/cli/pyobsd.py +0 -0
  16. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/cli/pyobsw.py +0 -0
  17. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/__init__.py +0 -0
  18. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/comm.py +0 -0
  19. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/commlogging.py +0 -0
  20. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/dummy/__init__.py +0 -0
  21. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/dummy/dummycomm.py +0 -0
  22. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/local/__init__.py +0 -0
  23. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/local/localcomm.py +0 -0
  24. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/local/localnetwork.py +0 -0
  25. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/proxy.py +0 -0
  26. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/xmpp/rpc.py +0 -0
  27. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/xmpp/xep_0009/__init__.py +0 -0
  28. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/xmpp/xep_0009/binding.py +0 -0
  29. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/xmpp/xep_0009/rpc.py +0 -0
  30. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/xmpp/xep_0009_timeout/__init__.py +0 -0
  31. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/xmpp/xep_0009_timeout/rpc.py +0 -0
  32. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/xmpp/xep_0009_timeout/stanza/RPC.py +0 -0
  33. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/xmpp/xep_0009_timeout/stanza/__init__.py +0 -0
  34. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/comm/xmpp/xmppclient.py +0 -0
  35. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/__init__.py +0 -0
  36. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/badweather.py +0 -0
  37. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/event.py +0 -0
  38. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/exposurestatuschanged.py +0 -0
  39. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/filterchanged.py +0 -0
  40. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/focusfound.py +0 -0
  41. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/goodweather.py +0 -0
  42. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/log.py +0 -0
  43. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/modechanged.py +0 -0
  44. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/moduleclosed.py +0 -0
  45. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/moduleopened.py +0 -0
  46. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/motionstatuschanged.py +0 -0
  47. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/move.py +0 -0
  48. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/newimage.py +0 -0
  49. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/newspectrum.py +0 -0
  50. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/offsets.py +0 -0
  51. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/roofclosing.py +0 -0
  52. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/roofopened.py +0 -0
  53. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/taskfailed.py +0 -0
  54. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/taskfinished.py +0 -0
  55. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/taskstarted.py +0 -0
  56. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/events/testevent.py +0 -0
  57. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/__init__.py +0 -0
  58. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/image.py +0 -0
  59. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/meta/__init__.py +0 -0
  60. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/meta/altazoffsets.py +0 -0
  61. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/meta/exptime.py +0 -0
  62. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/meta/genericoffset.py +0 -0
  63. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/meta/onskydistance.py +0 -0
  64. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/meta/pixeloffsets.py +0 -0
  65. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/meta/radecoffsets.py +0 -0
  66. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/meta/skyoffsets.py +0 -0
  67. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processor.py +0 -0
  68. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/__init__.py +0 -0
  69. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/_daobackgroundremover.py +0 -0
  70. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/annotation/__init__.py +0 -0
  71. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/annotation/_pillow.py +0 -0
  72. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/annotation/circle.py +0 -0
  73. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/annotation/crosshair.py +0 -0
  74. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/annotation/text.py +0 -0
  75. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/astrometry/__init__.py +0 -0
  76. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/astrometry/_dotnet_request.py +0 -0
  77. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/astrometry/_dotnet_request_builder.py +0 -0
  78. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/astrometry/_dotnet_request_logger.py +0 -0
  79. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/astrometry/_dotnet_response_saver.py +0 -0
  80. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/astrometry/astrometry.py +0 -0
  81. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/astrometry/dotnet.py +0 -0
  82. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/calibration/__init__.py +0 -0
  83. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/calibration/_calibration_cache.py +0 -0
  84. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/calibration/_ccddata_calibrator.py +0 -0
  85. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/calibration/calibration.py +0 -0
  86. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/detection/__init__.py +0 -0
  87. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/detection/_pysep_stats_calculator.py +0 -0
  88. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/detection/_source_catalog.py +0 -0
  89. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/detection/daophot.py +0 -0
  90. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/detection/pysep.py +0 -0
  91. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/detection/simpledisk.py +0 -0
  92. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/detection/sourcedetection.py +0 -0
  93. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/exptime/__init__.py +0 -0
  94. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/exptime/exptime.py +0 -0
  95. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/exptime/star.py +0 -0
  96. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/image/__init__.py +0 -0
  97. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/image/addfitsheaders.py +0 -0
  98. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/image/download.py +0 -0
  99. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/image/flip.py +0 -0
  100. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/image/grayscale.py +0 -0
  101. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/image/httpserver.py +0 -0
  102. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/image/normalize.py +0 -0
  103. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/image/save.py +0 -0
  104. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/image/saveimage.py +0 -0
  105. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/image/smooth.py +0 -0
  106. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/image/softbin.py +0 -0
  107. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/misc/__init__.py +0 -0
  108. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/misc/addmask.py +0 -0
  109. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/misc/catalogcircularmask.py +0 -0
  110. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/misc/circularmask.py +0 -0
  111. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/misc/createfilename.py +0 -0
  112. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/misc/imagesourcefilter.py +0 -0
  113. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/misc/removebackground.py +0 -0
  114. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/modules/__init__.py +0 -0
  115. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/modules/getfitsheaders.py +0 -0
  116. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/offsets/__init__.py +0 -0
  117. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/offsets/astrometry.py +0 -0
  118. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/offsets/brighteststar.py +0 -0
  119. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/offsets/brighteststar_guiding.py +0 -0
  120. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/offsets/dummyoffsets.py +0 -0
  121. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/offsets/dummyskyoffsets.py +0 -0
  122. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/offsets/fitsheader.py +0 -0
  123. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/offsets/offsets.py +0 -0
  124. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/offsets/pixeloffsets.py +0 -0
  125. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/offsets/projected.py +0 -0
  126. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/offsets/spilled_light.py +0 -0
  127. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/photometry/__init__.py +0 -0
  128. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/photometry/_photometry_calculator.py +0 -0
  129. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/photometry/_photutil_aperture_photometry.py +0 -0
  130. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/photometry/_sep_aperture_photometry.py +0 -0
  131. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/photometry/aperture_photometry.py +0 -0
  132. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/photometry/photometry.py +0 -0
  133. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/photometry/photutil.py +0 -0
  134. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/photometry/pysep.py +0 -0
  135. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/wcs/__init__.py +0 -0
  136. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/images/processors/wcs/solarhelioprojective.py +0 -0
  137. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IAbortable.py +0 -0
  138. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IAcquisition.py +0 -0
  139. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IAutoFocus.py +0 -0
  140. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IAutoGuiding.py +0 -0
  141. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IAutonomous.py +0 -0
  142. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IBinning.py +0 -0
  143. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/ICalibrate.py +0 -0
  144. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/ICamera.py +0 -0
  145. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IConfig.py +0 -0
  146. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/ICooling.py +0 -0
  147. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IData.py +0 -0
  148. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IDataSequence.py +0 -0
  149. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IDome.py +0 -0
  150. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IExposure.py +0 -0
  151. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IExposureTime.py +0 -0
  152. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IFilters.py +0 -0
  153. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IFitsHeaderAfter.py +0 -0
  154. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IFitsHeaderBefore.py +0 -0
  155. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IFlatField.py +0 -0
  156. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IFocusModel.py +0 -0
  157. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IFocuser.py +0 -0
  158. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IGain.py +0 -0
  159. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IImageFormat.py +0 -0
  160. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IImageType.py +0 -0
  161. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IMode.py +0 -0
  162. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IModule.py +0 -0
  163. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IMotion.py +0 -0
  164. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IMultiFiber.py +0 -0
  165. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IOffsetsAltAz.py +0 -0
  166. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IOffsetsRaDec.py +0 -0
  167. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IPointingAltAz.py +0 -0
  168. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IPointingBody.py +0 -0
  169. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IPointingHeliocentricPolar.py +0 -0
  170. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IPointingHeliographicStonyhurst.py +0 -0
  171. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IPointingHelioprojective.py +0 -0
  172. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IPointingOrbitalElements.py +0 -0
  173. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IPointingRaDec.py +0 -0
  174. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IPointingSeries.py +0 -0
  175. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IReady.py +0 -0
  176. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IRoof.py +0 -0
  177. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IRotation.py +0 -0
  178. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IRunnable.py +0 -0
  179. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IRunning.py +0 -0
  180. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IScriptRunner.py +0 -0
  181. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/ISpectrograph.py +0 -0
  182. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IStartStop.py +0 -0
  183. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IStructuredConfig.py +0 -0
  184. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/ISyncTarget.py +0 -0
  185. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/ITelescope.py +0 -0
  186. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/ITemperatures.py +0 -0
  187. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/ITrackingMode.py +0 -0
  188. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/ITrackingRate.py +0 -0
  189. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IVideo.py +0 -0
  190. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IWeather.py +0 -0
  191. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/IWindow.py +0 -0
  192. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/__init__.py +0 -0
  193. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/interfaces/interface.py +0 -0
  194. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/mixins/__init__.py +0 -0
  195. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/mixins/camerasettings.py +0 -0
  196. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/mixins/fitsheader.py +0 -0
  197. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/mixins/fitsnamespace.py +0 -0
  198. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/mixins/follow.py +0 -0
  199. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/mixins/motionstatus.py +0 -0
  200. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/mixins/pipeline.py +0 -0
  201. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/mixins/waitformotion.py +0 -0
  202. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/mixins/weatheraware.py +0 -0
  203. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/__init__.py +0 -0
  204. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/camera/__init__.py +0 -0
  205. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/camera/adaptive.py +0 -0
  206. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/camera/basecamera.py +0 -0
  207. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/camera/basespectrograph.py +0 -0
  208. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/camera/basevideo.py +0 -0
  209. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/camera/dummycamera.py +0 -0
  210. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/camera/dummyspectrograph.py +0 -0
  211. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/camera/dummyvideo.py +0 -0
  212. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/camera/pipelinecamera.py +0 -0
  213. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/flatfield/__init__.py +0 -0
  214. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/flatfield/flatfield.py +0 -0
  215. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/flatfield/pointing.py +0 -0
  216. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/flatfield/scheduler.py +0 -0
  217. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/focus/__init__.py +0 -0
  218. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/focus/dummyautofocus.py +0 -0
  219. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/focus/focusmodel.py +0 -0
  220. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/focus/focusseries.py +0 -0
  221. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/image/__init__.py +0 -0
  222. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/image/imagewatcher.py +0 -0
  223. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/image/imagewriter.py +0 -0
  224. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/image/pipeline.py +0 -0
  225. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/image/seeing.py +0 -0
  226. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/module.py +0 -0
  227. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/pointing/__init__.py +0 -0
  228. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/pointing/_base.py +0 -0
  229. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/pointing/_baseguiding.py +0 -0
  230. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/pointing/acquisition.py +0 -0
  231. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/pointing/autoguiding.py +0 -0
  232. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/pointing/dummyacquisition.py +0 -0
  233. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/pointing/dummyguiding.py +0 -0
  234. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/pointing/guidingstatistics/__init__.py +0 -0
  235. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/pointing/guidingstatistics/guidingstatistics.py +0 -0
  236. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/pointing/guidingstatistics/pixeloffset.py +0 -0
  237. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/pointing/guidingstatistics/skyoffsets.py +0 -0
  238. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/pointing/guidingstatistics/uptime.py +0 -0
  239. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/pointing/scienceframeguiding.py +0 -0
  240. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/robotic/__init__.py +0 -0
  241. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/robotic/mastermind.py +0 -0
  242. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/robotic/pointing.py +0 -0
  243. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/robotic/scheduler.py +0 -0
  244. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/robotic/scriptrunner.py +0 -0
  245. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/roof/__init__.py +0 -0
  246. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/roof/basedome.py +0 -0
  247. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/roof/baseroof.py +0 -0
  248. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/roof/dummyroof.py +0 -0
  249. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/telescope/__init__.py +0 -0
  250. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/telescope/_dummytelescopebase.py +0 -0
  251. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/telescope/basetelescope.py +0 -0
  252. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/telescope/dummyaltaztelescope.py +0 -0
  253. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/telescope/dummyradectelescope.py +0 -0
  254. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/telescope/dummysolartelescope.py +0 -0
  255. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/test/__init__.py +0 -0
  256. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/test/standalone.py +0 -0
  257. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/utils/__init__.py +0 -0
  258. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/utils/dummymode.py +0 -0
  259. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/utils/fluentlogger.py +0 -0
  260. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/utils/httpfilecache.py +0 -0
  261. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/utils/kiosk.py +0 -0
  262. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/utils/matrix.py +0 -0
  263. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/utils/stellarium.py +0 -0
  264. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/utils/telegram.py +0 -0
  265. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/utils/trigger.py +0 -0
  266. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/weather/__init__.py +0 -0
  267. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/weather/mockweather.py +0 -0
  268. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/weather/weather.py +0 -0
  269. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/weather/weather_api.py +0 -0
  270. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/modules/weather/weather_state.py +0 -0
  271. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/object.py +0 -0
  272. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/py.typed +0 -0
  273. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/__init__.py +0 -0
  274. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/observation.py +0 -0
  275. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/__init__.py +0 -0
  276. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/astroplanscheduler.py +0 -0
  277. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/constraints/__init__.py +0 -0
  278. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/constraints/airmassconstraint.py +0 -0
  279. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/constraints/constraint.py +0 -0
  280. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/constraints/moonilluminationconstraint.py +0 -0
  281. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/constraints/moonseparationconstraint.py +0 -0
  282. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/constraints/solarelevationconstraint.py +0 -0
  283. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/constraints/timeconstraint.py +0 -0
  284. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/dataprovider.py +0 -0
  285. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/merits/__init__.py +0 -0
  286. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/merits/aftertime.py +0 -0
  287. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/merits/beforetime.py +0 -0
  288. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/merits/constant.py +0 -0
  289. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/merits/follow.py +0 -0
  290. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/merits/interval.py +0 -0
  291. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/merits/merit.py +0 -0
  292. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/merits/pernight.py +0 -0
  293. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/merits/random.py +0 -0
  294. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/merits/timewindow.py +0 -0
  295. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/merits/transit.py +0 -0
  296. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/observationarchiveevolution.py +0 -0
  297. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/ondemandscheduler.py +0 -0
  298. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/targets/__init__.py +0 -0
  299. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/targets/dynamictarget.py +0 -0
  300. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/targets/heliocentricpolartarget.py +0 -0
  301. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/targets/helioprojectivetarget.py +0 -0
  302. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/targets/picker/__init__.py +0 -0
  303. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/targets/picker/csvpicker.py +0 -0
  304. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/targets/picker/picker.py +0 -0
  305. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/targets/siderealtarget.py +0 -0
  306. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/targets/target.py +0 -0
  307. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scheduler/taskscheduler.py +0 -0
  308. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/__init__.py +0 -0
  309. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/calibration/__init__.py +0 -0
  310. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/calibration/darkbias.py +0 -0
  311. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/calibration/pointing.py +0 -0
  312. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/calibration/skyflats.py +0 -0
  313. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/control/__init__.py +0 -0
  314. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/control/cases.py +0 -0
  315. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/control/conditional.py +0 -0
  316. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/control/parallel.py +0 -0
  317. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/control/selector.py +0 -0
  318. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/control/sequential.py +0 -0
  319. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/exceptions.py +0 -0
  320. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/imaging/__init__.py +0 -0
  321. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/imaging/autofocus.py +0 -0
  322. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/imaging/imaging.py +0 -0
  323. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/imaging/transitimaging.py +0 -0
  324. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/script.py +0 -0
  325. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/utils/__init__.py +0 -0
  326. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/utils/callmodule.py +0 -0
  327. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/utils/debugtrigger.py +0 -0
  328. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/scripts/utils/log.py +0 -0
  329. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/__init__.py +0 -0
  330. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/backend/__init__.py +0 -0
  331. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/backend/observationarchive.py +0 -0
  332. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/backend/taskarchive.py +0 -0
  333. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/filesystem/__init__.py +0 -0
  334. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/filesystem/observationarchive.py +0 -0
  335. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/filesystem/taskarchive.py +0 -0
  336. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/lco/__init__.py +0 -0
  337. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/lco/_portal.py +0 -0
  338. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/lco/_schedulereader.py +0 -0
  339. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/lco/_schedulewriter.py +0 -0
  340. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/lco/configdb.py +0 -0
  341. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/lco/mockobservationarchive.py +0 -0
  342. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/lco/observationarchive.py +0 -0
  343. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/lco/scripts/__init__.py +0 -0
  344. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/lco/scripts/autofocus.py +0 -0
  345. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/lco/scripts/default.py +0 -0
  346. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/lco/scripts/script.py +0 -0
  347. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/lco/task.py +0 -0
  348. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/lco/taskarchive.py +0 -0
  349. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/lco/taskrunner.py +0 -0
  350. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/memory/__init__.py +0 -0
  351. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/memory/observationarchive.py +0 -0
  352. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/memory/taskarchive.py +0 -0
  353. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/observationarchive.py +0 -0
  354. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/storage/taskarchive.py +0 -0
  355. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/task.py +0 -0
  356. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/taskrunner.py +0 -0
  357. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/utils/__init__.py +0 -0
  358. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/utils/archive/__init__.py +0 -0
  359. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/utils/archive/archive.py +0 -0
  360. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/utils/archive/local_archive.py +0 -0
  361. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/utils/archive/pyobs_archive.py +0 -0
  362. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/utils/exptime/__init__.py +0 -0
  363. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/utils/exptime/exptime.py +0 -0
  364. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/utils/exptime/stellarexptime.py +0 -0
  365. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/utils/skyflats/__init__.py +0 -0
  366. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/utils/skyflats/exptimeeval.py +0 -0
  367. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/utils/skyflats/flatfielder.py +0 -0
  368. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/utils/skyflats/pointing/__init__.py +0 -0
  369. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/utils/skyflats/pointing/base.py +0 -0
  370. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/utils/skyflats/pointing/static.py +0 -0
  371. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/utils/skyflats/priorities/__init__.py +0 -0
  372. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/utils/skyflats/priorities/archive.py +0 -0
  373. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/utils/skyflats/priorities/base.py +0 -0
  374. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/utils/skyflats/priorities/const.py +0 -0
  375. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/robotic/utils/skyflats/scheduler.py +0 -0
  376. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/__init__.py +0 -0
  377. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/average.py +0 -0
  378. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/cache.py +0 -0
  379. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/config.py +0 -0
  380. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/config_schema.py +0 -0
  381. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/coordinates.py +0 -0
  382. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/curvefit.py +0 -0
  383. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/enums.py +0 -0
  384. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/exceptions.py +0 -0
  385. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/fits.py +0 -0
  386. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/focusseries/__init__.py +0 -0
  387. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/focusseries/base.py +0 -0
  388. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/focusseries/photometry.py +0 -0
  389. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/focusseries/projection.py +0 -0
  390. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/grids/__init__.py +0 -0
  391. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/grids/filters.py +0 -0
  392. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/grids/grid.py +0 -0
  393. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/grids/gridnode.py +0 -0
  394. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/grids/pipeline.py +0 -0
  395. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/gui/__init__.py +0 -0
  396. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/gui/camera/__init__.py +0 -0
  397. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/gui/camera/binningwidget.py +0 -0
  398. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/gui/camera/datadisplaywidget.py +0 -0
  399. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/gui/camera/exposewidget.py +0 -0
  400. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/gui/camera/exposuretimewidget.py +0 -0
  401. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/gui/camera/imageformatwidget.py +0 -0
  402. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/gui/camera/listpickerdialog.py +0 -0
  403. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/gui/camera/windowingwidget.py +0 -0
  404. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/http.py +0 -0
  405. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/influxdb.py +0 -0
  406. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/logger.py +0 -0
  407. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/logging/__init__.py +0 -0
  408. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/logging/context.py +0 -0
  409. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/logging/resolvableerror.py +0 -0
  410. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/modulegui.py +0 -0
  411. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/offsets/__init__.py +0 -0
  412. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/offsets/applyaltazoffsets.py +0 -0
  413. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/offsets/applyoffsets.py +0 -0
  414. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/offsets/applyradecoffsets.py +0 -0
  415. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/parallel.py +0 -0
  416. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/pipeline/__init__.py +0 -0
  417. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/pipeline/night.py +0 -0
  418. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/pipeline/pipeline.py +0 -0
  419. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/publisher/__init__.py +0 -0
  420. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/publisher/csv.py +0 -0
  421. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/publisher/http.py +0 -0
  422. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/publisher/log.py +0 -0
  423. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/publisher/multi.py +0 -0
  424. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/publisher/publisher.py +0 -0
  425. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/serialization.py +0 -0
  426. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/shellcommand.py +0 -0
  427. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/threads/__init__.py +0 -0
  428. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/threads/lockwithabort.py +0 -0
  429. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/time.py +0 -0
  430. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/utils/units.py +0 -0
  431. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/version.py +0 -0
  432. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/vfs/__init__.py +0 -0
  433. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/vfs/archivefile.py +0 -0
  434. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/vfs/bufferedfile.py +0 -0
  435. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/vfs/file.py +0 -0
  436. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/vfs/filelists/__init__.py +0 -0
  437. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/vfs/filelists/filelist.py +0 -0
  438. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/vfs/filelists/testing.py +0 -0
  439. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/vfs/httpfile.py +0 -0
  440. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/vfs/localfile.py +0 -0
  441. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/vfs/memfile.py +0 -0
  442. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/vfs/sftpfile.py +0 -0
  443. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/vfs/smbfile.py +0 -0
  444. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/vfs/sshfile.py +0 -0
  445. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/vfs/tempfile.py +0 -0
  446. {pyobs_core-2.0.0.dev48 → pyobs_core-2.0.0.dev50}/pyobs/vfs/vfs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyobs-core
3
- Version: 2.0.0.dev48
3
+ Version: 2.0.0.dev50
4
4
  Summary: robotic telescope software
5
5
  Author-email: Tim-Oliver Husser <thusser@uni-goettingen.de>
6
6
  License-Expression: MIT
@@ -20,7 +20,7 @@ Requires-Dist: logging-journald>=0.6.11
20
20
  Requires-Dist: numpy<3,>=2.2.5
21
21
  Requires-Dist: pandas>=2.2.3
22
22
  Requires-Dist: paramiko>=3.5.1
23
- Requires-Dist: pillow>=11.3.0
23
+ Requires-Dist: pillow>=12.3.0
24
24
  Requires-Dist: psutil>=7.2.2
25
25
  Requires-Dist: py-expression-eval<0.4,>=0.3.14
26
26
  Requires-Dist: pydantic>=2.12.5
@@ -5,6 +5,7 @@ import os
5
5
  import platform
6
6
  import signal
7
7
  import warnings
8
+ from collections.abc import Awaitable, Callable
8
9
  from io import StringIO
9
10
  from typing import Any, TypedDict
10
11
 
@@ -56,7 +57,9 @@ class Application:
56
57
 
57
58
  def __init__(
58
59
  self,
59
- config: str,
60
+ config: str | None = None,
61
+ module_factory: Callable[[], Awaitable[Module]] | None = None,
62
+ loop_module_class: type[Module] | None = None,
60
63
  log_file: str | None = None,
61
64
  log_level: str = "info",
62
65
  syslog: bool = False,
@@ -65,18 +68,36 @@ class Application:
65
68
  ):
66
69
  """Initializes a pyobs application.
67
70
 
71
+ Exactly one of `config`/`module_factory` must be given.
72
+
68
73
  Args:
69
74
  config: Name of config file.
75
+ module_factory: Async factory that builds the Module once the event loop is already
76
+ running, instead of parsing a config file synchronously in __init__ -- lets the
77
+ factory `await` things (e.g. a login dialog's submit signal) before the module
78
+ (and its comm connection) exists at all. Mutually exclusive with `config`.
79
+ loop_module_class: Module class to pick `new_event_loop()` from when `module_factory`
80
+ is given (the caller already knows which module class it's about to build, e.g.
81
+ `GUI` in pyobs-gui's case). Required when `module_factory` is given, ignored
82
+ otherwise.
70
83
  log_file: Name of log file, if any.
71
84
  log_level: Logging level.
72
85
  syslog: Send log to systemd journal, tagged with SYSLOG_IDENTIFIER=pyobs
73
86
  and PYOBS_MODULE=<module name>. Requires logging-journald.
74
87
  influx_log: Log to influx DB.
75
88
  """
89
+ if (config is None) == (module_factory is None):
90
+ raise ValueError("Exactly one of 'config' or 'module_factory' must be given.")
91
+ if module_factory is not None and loop_module_class is None:
92
+ raise ValueError("loop_module_class is required when module_factory is given.")
76
93
 
77
94
  # get config name without path and extension
78
95
  self._config = config
79
- config_base = os.path.splitext(os.path.basename(config))[0]
96
+ config_base = (
97
+ os.path.splitext(os.path.basename(config))[0]
98
+ if config is not None
99
+ else loop_module_class.__name__.lower() # type: ignore[union-attr]
100
+ )
80
101
 
81
102
  # filter that injects %(pyobs_module)s into every LogRecord from the context var
82
103
  module_name_filter = ModuleNameFilter()
@@ -156,72 +177,100 @@ class Application:
156
177
 
157
178
  log = logging.getLogger(__name__)
158
179
 
159
- # set (maybe temporary) module name to filename without leading underscores
160
- module_name.set(Path(self._config).stem.lstrip("_"))
161
-
162
- # load config
163
- log.info("Loading configuration from %s...", self._config)
164
- with StringIO(pre_process_yaml(self._config)) as f:
165
- cfg: dict[str, Any] = yaml.safe_load(f)
166
-
167
- # get module class
168
- class_name = cfg["class"]
169
- klass = get_class_from_string(class_name)
170
-
171
- # create event loop — if top-level class doesn't override new_event_loop,
172
- # check child modules for one that does (e.g. pyobs_gui.GUI in a MultiModule)
173
- loop_class = klass
174
- if klass.new_event_loop is Module.new_event_loop:
175
- for mod_cfg in cfg.get("modules", {}).values():
176
- if isinstance(mod_cfg, dict) and "class" in mod_cfg:
177
- child_klass = get_class_from_string(mod_cfg["class"])
178
- if child_klass.new_event_loop is not Module.new_event_loop:
179
- loop_class = child_klass
180
- break
181
- self._loop = loop_class.new_event_loop()
182
- asyncio.set_event_loop(self._loop)
183
-
184
- # create module and open it
185
- config_stem = Path(self._config).stem.lstrip("_")
186
- if "comm" not in cfg and not issubclass(klass, MultiModule):
187
- # no comm configured -- module would otherwise fall back to a DummyComm named
188
- # with the fixed placeholder "module", indistinguishable from every other
189
- # comm-less module in PYOBS_MODULE log tagging (see
190
- # specs/steering/finding-module-logs-under-pyobsd.md) and guaranteed to trip the
191
- # stem-mismatch check below. Give it the config file's own stem instead.
192
- cfg = {**cfg, "comm": {"class": f"{DummyComm.__module__}.{DummyComm.__name__}", "name": config_stem}}
193
- log.info("Creating module from class %s...", klass.__name__)
194
- self._module = get_object(cfg, Module)
195
-
196
- # a config file whose stem doesn't match its own module's name silently splits
197
- # PYOBS_MODULE log tagging in two: logging that falls through both execute()
198
- # (module.py) and BackgroundTask (background_task.py) stays on the filename set
199
- # above, while logging covered by either of those uses the module's real
200
- # Comm-derived name -- refuse to start rather than let that drift run undetected.
201
- # MultiModule is exempt: its own .name is always the fixed placeholder "multi"
202
- # (module.py), never meant to match a filename -- each child module is already
203
- # correctly distinguished via execute()/BackgroundTask.
204
- if not isinstance(self._module, MultiModule):
205
- if self._module.name != config_stem:
206
- log.warning(
207
- "Config file stem (%s) does not match module's own name "
208
- "(%s). Rename the config file or fix the module's "
209
- "comm configuration so they match.",
210
- config_stem,
211
- self._module.name,
212
- )
180
+ # set (maybe temporary) module name -- filename without leading underscores for the
181
+ # config path, or a placeholder derived from loop_module_class for the factory path
182
+ # (there's no real name yet until the factory resolves the actual module/comm identity)
183
+ module_name.set(Path(self._config).stem.lstrip("_") if self._config is not None else config_base)
184
+
185
+ self._module: Module | None = None
186
+ self._module_factory = module_factory
187
+
188
+ if config is not None:
189
+ # load config
190
+ log.info("Loading configuration from %s...", config)
191
+ with StringIO(pre_process_yaml(config)) as f:
192
+ cfg: dict[str, Any] = yaml.safe_load(f)
193
+
194
+ # get module class
195
+ class_name = cfg["class"]
196
+ klass = get_class_from_string(class_name)
197
+
198
+ # create event loop — if top-level class doesn't override new_event_loop,
199
+ # check child modules for one that does (e.g. pyobs_gui.GUI in a MultiModule)
200
+ loop_class = klass
201
+ if klass.new_event_loop is Module.new_event_loop:
202
+ for mod_cfg in cfg.get("modules", {}).values():
203
+ if isinstance(mod_cfg, dict) and "class" in mod_cfg:
204
+ child_klass = get_class_from_string(mod_cfg["class"])
205
+ if child_klass.new_event_loop is not Module.new_event_loop:
206
+ loop_class = child_klass
207
+ break
208
+ self._loop = loop_class.new_event_loop()
209
+ asyncio.set_event_loop(self._loop)
210
+
211
+ # create module and open it
212
+ config_stem = Path(config).stem.lstrip("_")
213
+ if "comm" not in cfg and not issubclass(klass, MultiModule):
214
+ # no comm configured -- module would otherwise fall back to a DummyComm named
215
+ # with the fixed placeholder "module", indistinguishable from every other
216
+ # comm-less module in PYOBS_MODULE log tagging (see
217
+ # specs/steering/finding-module-logs-under-pyobsd.md) and guaranteed to trip the
218
+ # stem-mismatch check below. Give it the config file's own stem instead.
219
+ cfg = {**cfg, "comm": {"class": f"{DummyComm.__module__}.{DummyComm.__name__}", "name": config_stem}}
220
+ log.info("Creating module from class %s...", klass.__name__)
221
+ self._module = get_object(cfg, Module)
222
+
223
+ # a config file whose stem doesn't match its own module's name silently splits
224
+ # PYOBS_MODULE log tagging in two: logging that falls through both execute()
225
+ # (module.py) and BackgroundTask (background_task.py) stays on the filename set
226
+ # above, while logging covered by either of those uses the module's real
227
+ # Comm-derived name -- refuse to start rather than let that drift run undetected.
228
+ # MultiModule is exempt: its own .name is always the fixed placeholder "multi"
229
+ # (module.py), never meant to match a filename -- each child module is already
230
+ # correctly distinguished via execute()/BackgroundTask.
231
+ if not isinstance(self._module, MultiModule):
232
+ if self._module.name != config_stem:
233
+ log.warning(
234
+ "Config file stem (%s) does not match module's own name "
235
+ "(%s). Rename the config file or fix the module's "
236
+ "comm configuration so they match.",
237
+ config_stem,
238
+ self._module.name,
239
+ )
240
+ else:
241
+ # module_factory path: pick the event loop from the class the caller already knows
242
+ # it's about to build -- the module itself gets built later, inside the running
243
+ # loop (see _main()), since only there can the factory await anything.
244
+ assert loop_module_class is not None # validated above
245
+ self._loop = loop_module_class.new_event_loop()
246
+ asyncio.set_event_loop(self._loop)
213
247
 
214
248
  def run(self) -> None:
215
249
  """Run app."""
216
250
 
251
+ # run main task forever -- created before the signal handlers are registered below, so
252
+ # a signal arriving the instant it's registered can never find self._main_task unset
253
+ self._main_task = self._loop.create_task(self._main())
254
+
217
255
  # signals
218
256
  for sig in (signal.SIGTERM, signal.SIGINT):
219
257
  self._loop.add_signal_handler(sig, self._signal_handler, sig)
220
258
 
221
- # run main task forever
222
- main = self._loop.create_task(self._main())
223
259
  self._loop.run_forever()
224
- self._loop.run_until_complete(main)
260
+ try:
261
+ self._loop.run_until_complete(self._main_task)
262
+ except asyncio.CancelledError:
263
+ # _signal_handler() cancelled _main_task directly -- only reachable while
264
+ # module_factory hadn't resolved yet (e.g. a login dialog still open), since
265
+ # self._module.quit() is what normally unblocks run_forever()/main otherwise.
266
+ pass
267
+ except RuntimeError:
268
+ # Qt-backed loops (qasync, used by pyobs-gui) can report the underlying Qt event
269
+ # loop as stopped before _main_task is done -- e.g. when the GUI's native window
270
+ # close triggers Qt's own quit independently of self._module.quit(). _main_task
271
+ # keeps running as a plain asyncio task regardless; the cancel/gather below still
272
+ # drains it, so this is a benign race in loop bookkeeping, not a failed shutdown.
273
+ log.warning("Event loop reported as stopped before shutdown finished; continuing cleanup.")
225
274
 
226
275
  # main finished, cancel all tasks
227
276
  tasks = asyncio.all_tasks(self._loop)
@@ -229,7 +278,11 @@ class Application:
229
278
  log.debug("Task %s still running, cancelling it...", t)
230
279
  t.cancel()
231
280
  group = asyncio.gather(*tasks, return_exceptions=True)
232
- self._loop.run_until_complete(group)
281
+ try:
282
+ self._loop.run_until_complete(group)
283
+ except RuntimeError:
284
+ # same benign qasync race as above, now on the cancellation gather itself.
285
+ log.warning("Event loop reported as stopped before task cancellation finished.")
233
286
 
234
287
  # finished
235
288
  log.info("Closing loop...")
@@ -238,7 +291,14 @@ class Application:
238
291
  def _signal_handler(self, sig: int) -> None:
239
292
  """React to signals and quit the module."""
240
293
 
241
- self._module.quit()
294
+ if self._module is not None:
295
+ self._module.quit()
296
+ else:
297
+ # module_factory hasn't resolved yet (e.g. a login dialog still open) -- nothing to
298
+ # quit, so cancel the pending factory wait directly instead. Module.quit() would
299
+ # otherwise have called loop.stop() itself (module.py); mirror that here too.
300
+ self._main_task.cancel()
301
+ self._loop.stop()
242
302
 
243
303
  # reset signal handlers
244
304
  log.info("Got signal: %s, shutting down.", sig)
@@ -251,6 +311,15 @@ class Application:
251
311
 
252
312
  # everything in a try/except/finally, so that we can shut down gracefully
253
313
  try:
314
+ # resolve the module from the factory, if we're in that mode and haven't yet --
315
+ # runs inside the running loop, so the factory can await anything it needs to
316
+ # (e.g. a login dialog's submit signal) before the module/comm connection exists
317
+ if self._module is None:
318
+ assert self._module_factory is not None # validated in __init__
319
+ log.info("Waiting for module factory to resolve...")
320
+ self._module = await self._module_factory()
321
+ log.info("Module factory resolved.")
322
+
254
323
  # open module
255
324
  log.info("Opening module...")
256
325
  await self._module.startup()
@@ -0,0 +1,3 @@
1
+ from .xmppcomm import XmppComm, is_valid_jid
2
+
3
+ __all__ = ["XmppComm", "is_valid_jid"]
@@ -78,6 +78,13 @@ def value_to_xml(value: Any, type_hint: Any) -> ET.Element:
78
78
  elem = ET.Element("nil")
79
79
  return elem
80
80
 
81
+ # Coerce to the declared `str` type when the runtime value doesn't match it (e.g. a
82
+ # stray bool leaking through code that violates a `dict[str, str]`'s own contract) --
83
+ # otherwise the isinstance-based dispatch below wins regardless of what the field
84
+ # actually declares, and the value's Python type silently corrupts across the wire.
85
+ if type_hint is str and not isinstance(value, str):
86
+ value = str(value)
87
+
81
88
  # bool (before int — bool is subclass of int)
82
89
  if isinstance(value, bool):
83
90
  elem = ET.Element("boolean")
@@ -108,6 +115,14 @@ def value_to_xml(value: Any, type_hint: Any) -> ET.Element:
108
115
  elem.text = value.value
109
116
  return elem
110
117
 
118
+ # Time (pyobs's astropy.time.Time subclass) -- serialize as ISO text, reconstructed on
119
+ # decode via type_hint (mirrors the StrEnum pattern above). Without this, Time fell
120
+ # through to the generic stringify fallback below with no way back to a Time object.
121
+ if isinstance(value, _Time):
122
+ elem = ET.Element("string")
123
+ elem.text = value.isot
124
+ return elem
125
+
111
126
  # dataclass — serialize as <{namespace}state> with plain field children
112
127
  if dataclasses.is_dataclass(value) and not isinstance(value, type):
113
128
  return _dataclass_to_xml(value)
@@ -184,20 +199,34 @@ def xml_to_value(elem: ET.Element, type_hint: Any) -> Any:
184
199
  if tag == "nil":
185
200
  return None
186
201
 
202
+ # boolean/int/double coerced to str when the field is declared `str` but a
203
+ # not-yet-upgraded sender is still emitting the wrong wire tag for it (the mirror image
204
+ # of value_to_xml's own str coercion on encode -- see the comment there) -- otherwise a
205
+ # bool/int/float leaks straight into a str-typed field/dict value.
187
206
  if tag == "boolean":
188
- return elem.text == "true"
207
+ value = elem.text == "true"
208
+ return str(value) if type_hint is str else value
189
209
 
190
210
  if tag == "int":
191
- return int(elem.text) if elem.text is not None else 0
211
+ value = int(elem.text) if elem.text is not None else 0
212
+ return str(value) if type_hint is str else value
192
213
 
193
214
  if tag == "double":
194
- return float(elem.text) if elem.text is not None else 0.0
215
+ value = float(elem.text) if elem.text is not None else 0.0
216
+ return str(value) if type_hint is str else value
195
217
 
196
218
  if tag == "string":
197
219
  text = elem.text or ""
198
220
  # cast to enum if type_hint is a StrEnum subclass
199
221
  if type_hint and isinstance(type_hint, type) and issubclass(type_hint, StrEnum):
200
222
  return type_hint(text)
223
+ # cast to Time if type_hint is a Time subclass (see the matching encode branch).
224
+ # No explicit format=: astropy auto-detects "isot" (this fix's own encoding) as well
225
+ # as "iso" (the plain str(Time) space-separated format a not-yet-upgraded sender on
226
+ # the old serializer would still be emitting via the stringify fallback) -- pinning
227
+ # to "isot" would hard-fail on exactly that mixed-version case.
228
+ if type_hint and isinstance(type_hint, type) and issubclass(type_hint, _Time):
229
+ return type_hint(text)
201
230
  return text
202
231
 
203
232
  if tag == "items":
@@ -340,6 +369,8 @@ def _parse_scalar(text: str, type_hint: Any) -> Any:
340
369
  return float(text)
341
370
  if isinstance(type_hint, type) and issubclass(type_hint, StrEnum):
342
371
  return type_hint(text)
372
+ if isinstance(type_hint, type) and issubclass(type_hint, _Time):
373
+ return type_hint(text)
343
374
  return text
344
375
 
345
376
 
@@ -48,6 +48,26 @@ def _retry_delay(attempt: int, cap: float = 30.0, base: float = 1.0) -> float:
48
48
  return random.uniform(0, min(cap, base * (2**attempt)))
49
49
 
50
50
 
51
+ # anchored at both ends -- re.match alone doesn't anchor the end, so e.g. "user@domain/res/extra"
52
+ # would otherwise still "match" as a valid prefix
53
+ _JID_RE = re.compile(r"([\w_\-\.]+)@([\w_\-\.]+)/([\w_\-\.]+)$")
54
+
55
+
56
+ def is_valid_jid(jid: str, resource: str = "pyobs") -> bool:
57
+ """Whether jid is a valid user@domain or user@domain/resource JID -- exactly what
58
+ XmppComm.__init__ requires, so callers taking raw user input (e.g. a login window) can
59
+ validate it up front instead of finding out via a raised, less specific exception.
60
+
61
+ Args:
62
+ jid: JID to check, with or without a resource part.
63
+ resource: Resource to assume if jid doesn't already include one -- must match whatever
64
+ XmppComm itself would be constructed with, since a resource-less jid is only valid
65
+ if *some* resource ends up attached to it.
66
+ """
67
+ candidate = jid if "/" in jid else f"{jid}/{resource}"
68
+ return bool(_JID_RE.match(candidate))
69
+
70
+
51
71
  class EventStanza(ElementBase):
52
72
  name = "event"
53
73
  namespace = "pyobs:event"
@@ -180,10 +200,10 @@ class XmppComm(Comm):
180
200
  jid += "/" + resource
181
201
 
182
202
  # get user/domain/resource and write it back to config
183
- m = re.match(r"([\w_\-\.]+)@([\w_\-\.]+)\/([\w_\-\.]+)", jid)
203
+ m = _JID_RE.match(jid)
184
204
  if not m:
185
- log.error("Invalid JID format.")
186
- raise ValueError()
205
+ log.error("Invalid JID format: %r (expected user@domain or user@domain/resource).", jid)
206
+ raise ValueError(f"Invalid JID format: {jid!r} (expected user@domain or user@domain/resource).")
187
207
  self._user = m.group(1)
188
208
  self._domain = m.group(2)
189
209
  self._resource = m.group(3)
@@ -1161,4 +1181,4 @@ class XmppComm(Comm):
1161
1181
  pass # already gone server-side
1162
1182
 
1163
1183
 
1164
- __all__ = ["XmppComm"]
1184
+ __all__ = ["XmppComm", "is_valid_jid"]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pyobs-core"
3
- version = "2.0.0.dev48"
3
+ version = "2.0.0.dev50"
4
4
  description = "robotic telescope software"
5
5
  authors = [{ name = "Tim-Oliver Husser", email = "thusser@uni-goettingen.de" }]
6
6
  requires-python = ">=3.11"
@@ -27,7 +27,7 @@ dependencies = [
27
27
  "influxdb-client>=1.49.0",
28
28
  "pydantic>=2.12.5",
29
29
  "astropydantic>=0.0.5",
30
- "pillow>=11.3.0",
30
+ "pillow>=12.3.0",
31
31
  "tenacity>=9.1.4",
32
32
  "psutil>=7.2.2",
33
33
  "logging-journald>=0.6.11",
@@ -1,3 +0,0 @@
1
- from .xmppcomm import XmppComm
2
-
3
- __all__ = ["XmppComm"]