pyobs-core 2.2.0__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/PKG-INFO +1 -1
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/camera/dummycamera.py +21 -2
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/focus/focusseries.py +2 -2
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/image/imagewatcher.py +61 -25
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/module.py +2 -2
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/robotic/scheduler.py +58 -17
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/roof/dummyroof.py +1 -1
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/telescope/basetelescope.py +5 -2
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/filesystem/observationarchive.py +6 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/portal/observationarchive.py +38 -5
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/portal/taskarchive.py +8 -4
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/task.py +4 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/pipeline/reduction.py +9 -4
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/serialization.py +49 -3
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyproject.toml +1 -1
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/.gitignore +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/LICENSE +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/application.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/background_task.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/cli/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/cli/_cli.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/cli/pyobs.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/cli/pyobsd.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/cli/pyobsw.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/comm.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/commlogging.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/dummy/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/dummy/dummycomm.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/local/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/local/localcomm.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/local/localnetwork.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/proxy.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/xmpp/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/xmpp/rpc.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/xmpp/serializer.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/xmpp/xep_0009/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/xmpp/xep_0009/binding.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/xmpp/xep_0009/rpc.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/xmpp/xep_0009_timeout/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/xmpp/xep_0009_timeout/rpc.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/xmpp/xep_0009_timeout/stanza/RPC.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/xmpp/xep_0009_timeout/stanza/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/xmpp/xmppclient.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/comm/xmpp/xmppcomm.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/badweather.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/event.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/exposurestatuschanged.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/filterchanged.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/focusfound.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/goodweather.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/log.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/modechanged.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/moduleclosed.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/moduleopened.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/motionstatuschanged.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/move.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/newimage.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/newspectrum.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/offsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/roofclosing.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/roofopened.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/taskfailed.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/taskfinished.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/taskstarted.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/events/testevent.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/image.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/meta/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/meta/altazoffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/meta/exptime.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/meta/genericoffset.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/meta/onskydistance.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/meta/pixeloffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/meta/radecoffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/meta/skyoffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processor.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/_daobackgroundremover.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/annotation/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/annotation/_pillow.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/annotation/circle.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/annotation/crosshair.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/annotation/text.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/astrometry/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/astrometry/_dotnet_request.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/astrometry/_dotnet_request_builder.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/astrometry/_dotnet_request_logger.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/astrometry/_dotnet_response_saver.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/astrometry/astrometry.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/astrometry/dotnet.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/calibration/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/calibration/_calibration_cache.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/calibration/_ccddata_calibrator.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/calibration/calibration.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/detection/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/detection/_pysep_stats_calculator.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/detection/_source_catalog.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/detection/daophot.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/detection/pysep.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/detection/simpledisk.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/detection/sourcedetection.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/exptime/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/exptime/exptime.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/exptime/star.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/image/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/image/addfitsheaders.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/image/download.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/image/flip.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/image/grayscale.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/image/httpserver.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/image/normalize.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/image/save.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/image/saveimage.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/image/smooth.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/image/softbin.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/misc/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/misc/addmask.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/misc/catalogcircularmask.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/misc/circularmask.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/misc/createfilename.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/misc/imagesourcefilter.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/misc/removebackground.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/modules/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/modules/getfitsheaders.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/offsets/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/offsets/astrometry.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/offsets/brighteststar.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/offsets/brighteststar_guiding.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/offsets/dummyoffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/offsets/dummyskyoffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/offsets/fitsheader.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/offsets/offsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/offsets/pixeloffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/offsets/projected.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/offsets/spilled_light.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/photometry/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/photometry/_photometry_calculator.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/photometry/_photutil_aperture_photometry.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/photometry/_sep_aperture_photometry.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/photometry/aperture_photometry.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/photometry/photometry.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/photometry/photutil.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/photometry/pysep.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/wcs/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/images/processors/wcs/solarhelioprojective.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IAbortable.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IAcquisition.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IAutoFocus.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IAutoGuiding.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IAutonomous.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IBinning.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/ICalibrate.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/ICamera.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IConfig.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/ICooling.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IData.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IDataSequence.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IDome.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IExposure.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IExposureTime.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IFilters.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IFitsHeaderAfter.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IFitsHeaderBefore.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IFlatField.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IFocusModel.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IFocuser.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IGain.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IImageFormat.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IImageType.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IMode.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IModule.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IMotion.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IMultiFiber.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IOffsetsAltAz.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IOffsetsRaDec.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IPointingAltAz.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IPointingBody.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IPointingHeliocentricPolar.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IPointingHeliographicStonyhurst.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IPointingHelioprojective.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IPointingOrbitalElements.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IPointingRaDec.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IPointingSeries.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IReady.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IRobotic.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IRoboticScheduler.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IRoof.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IRotation.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IRunnable.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IRunning.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IScriptRunner.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/ISpectrograph.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IStartStop.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IStructuredConfig.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/ISyncTarget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/ITelescope.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/ITemperatures.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/ITrackingMode.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/ITrackingRate.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IVideo.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IWeather.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/IWindow.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/interfaces/interface.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/mixins/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/mixins/camerasettings.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/mixins/fitsheader.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/mixins/fitsnamespace.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/mixins/follow.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/mixins/motionstatus.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/mixins/pipeline.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/mixins/waitformotion.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/mixins/weatheraware.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/camera/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/camera/adaptive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/camera/basecamera.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/camera/basespectrograph.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/camera/basevideo.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/camera/dummyspectrograph.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/camera/dummyvideo.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/camera/pipelinecamera.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/flatfield/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/flatfield/flatfield.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/flatfield/pointing.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/flatfield/scheduler.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/focus/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/focus/dummyautofocus.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/focus/focusmodel.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/image/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/image/imagewriter.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/image/pipeline.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/image/seeing.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/pointing/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/pointing/_base.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/pointing/_baseguiding.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/pointing/acquisition.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/pointing/autoguiding.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/pointing/dummyacquisition.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/pointing/dummyguiding.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/pointing/guidingstatistics/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/pointing/guidingstatistics/guidingstatistics.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/pointing/guidingstatistics/pixeloffset.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/pointing/guidingstatistics/skyoffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/pointing/guidingstatistics/uptime.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/pointing/scienceframeguiding.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/robotic/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/robotic/dummymastermind.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/robotic/dummyscheduler.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/robotic/mastermind.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/robotic/pointing.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/robotic/scriptrunner.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/roof/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/roof/basedome.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/roof/baseroof.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/telescope/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/telescope/_dummytelescopebase.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/telescope/dummyaltaztelescope.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/telescope/dummyradectelescope.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/telescope/dummysolartelescope.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/test/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/test/standalone.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/utils/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/utils/dummymode.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/utils/dummystructuredconfig.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/utils/fluentlogger.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/utils/httpfilecache.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/utils/kiosk.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/utils/matrix.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/utils/stellarium.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/utils/telegram.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/utils/trigger.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/weather/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/weather/mockweather.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/weather/weather.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/weather/weather_api.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/modules/weather/weather_state.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/object.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/py.typed +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/observation.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/_executor.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/astroplanscheduler.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/constraints/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/constraints/airmassconstraint.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/constraints/constraint.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/constraints/moonilluminationconstraint.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/constraints/moonseparationconstraint.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/constraints/solarelevationconstraint.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/constraints/timeconstraint.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/dataprovider.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/merits/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/merits/aftertime.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/merits/beforetime.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/merits/constant.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/merits/follow.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/merits/interval.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/merits/merit.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/merits/pernight.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/merits/random.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/merits/timewindow.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/merits/transit.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/observationarchiveevolution.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/ondemandscheduler.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/targets/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/targets/dynamictarget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/targets/heliocentricpolartarget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/targets/helioprojectivetarget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/targets/picker/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/targets/picker/csvpicker.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/targets/picker/picker.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/targets/siderealtarget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/targets/target.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scheduler/taskscheduler.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/calibration/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/calibration/darkbias.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/calibration/pointing.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/calibration/skyflats.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/control/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/control/cases.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/control/conditional.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/control/parallel.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/control/selector.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/control/sequential.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/exceptions.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/imaging/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/imaging/autofocus.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/imaging/imaging.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/imaging/transitimaging.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/script.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/utils/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/utils/callmodule.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/utils/debugtrigger.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/scripts/utils/log.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/filesystem/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/filesystem/taskarchive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/lco/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/lco/_portal.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/lco/_schedulereader.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/lco/_schedulewriter.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/lco/configdb.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/lco/mockobservationarchive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/lco/observationarchive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/lco/scripts/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/lco/scripts/autofocus.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/lco/scripts/default.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/lco/scripts/script.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/lco/task.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/lco/taskarchive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/lco/taskrunner.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/memory/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/memory/observationarchive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/memory/taskarchive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/observationarchive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/portal/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/taskarchive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/taskrunner.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/archive/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/archive/archive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/archive/local_archive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/archive/pyobs_archive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/calibration.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/exptime/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/exptime/exptime.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/exptime/stellarexptime.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/skyflats/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/skyflats/exptimeeval.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/skyflats/flatfielder.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/skyflats/pointing/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/skyflats/pointing/base.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/skyflats/pointing/static.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/skyflats/priorities/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/skyflats/priorities/archive.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/skyflats/priorities/base.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/skyflats/priorities/const.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/utils/skyflats/scheduler.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/average.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/cache.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/config.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/config_schema.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/coordinates.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/curvefit.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/enums.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/exceptions.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/exptime_grouping.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/fits.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/focusseries/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/focusseries/base.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/focusseries/photometry.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/focusseries/projection.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/grids/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/grids/filters.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/grids/grid.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/grids/gridnode.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/grids/pipeline.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/gui/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/gui/camera/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/gui/camera/binningwidget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/gui/camera/datadisplaywidget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/gui/camera/exposewidget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/gui/camera/exposuretimewidget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/gui/camera/imageformatwidget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/gui/camera/listpickerdialog.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/gui/camera/windowingwidget.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/http.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/influxdb.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/logger.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/logging/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/logging/context.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/logging/resolvableerror.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/modulegui.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/offsets/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/offsets/applyaltazoffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/offsets/applyoffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/offsets/applyradecoffsets.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/parallel.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/pipeline/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/pipeline/pipeline.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/pipeline/progress.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/pipeline/reduction_base.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/publisher/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/publisher/csv.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/publisher/http.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/publisher/log.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/publisher/multi.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/publisher/publisher.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/shellcommand.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/threads/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/threads/lockwithabort.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/time.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/units.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/utils/versions.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/version.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/vfs/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/vfs/archivefile.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/vfs/bufferedfile.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/vfs/file.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/vfs/filelists/__init__.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/vfs/filelists/filelist.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/vfs/filelists/testing.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/vfs/httpfile.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/vfs/localfile.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/vfs/memfile.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/vfs/sftpfile.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/vfs/smbfile.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/vfs/sshfile.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/vfs/tempfile.py +0 -0
- {pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/vfs/vfs.py +0 -0
|
@@ -28,9 +28,11 @@ from pyobs.interfaces import (
|
|
|
28
28
|
IImageFormat,
|
|
29
29
|
ImageFormatCapabilities,
|
|
30
30
|
ImageFormatState,
|
|
31
|
+
IMotion,
|
|
31
32
|
IPointingRaDec,
|
|
32
33
|
ITemperatures,
|
|
33
34
|
IWindow,
|
|
35
|
+
MotionState,
|
|
34
36
|
RaDecState,
|
|
35
37
|
SensorReading,
|
|
36
38
|
TemperaturesState,
|
|
@@ -39,7 +41,7 @@ from pyobs.interfaces import (
|
|
|
39
41
|
)
|
|
40
42
|
from pyobs.modules.camera.basecamera import BaseCamera
|
|
41
43
|
from pyobs.utils import exceptions as exc
|
|
42
|
-
from pyobs.utils.enums import ExposureStatus, ImageFormat, ImageType
|
|
44
|
+
from pyobs.utils.enums import ExposureStatus, ImageFormat, ImageType, MotionStatus
|
|
43
45
|
from pyobs.utils.time import Time
|
|
44
46
|
|
|
45
47
|
log = logging.getLogger(__name__)
|
|
@@ -73,6 +75,7 @@ class DummyCamera(BaseCamera, IWindow, IBinning, ICooling, IGain, IImageFormat):
|
|
|
73
75
|
max_mag: float = 20.0,
|
|
74
76
|
seeing: float = 3.0,
|
|
75
77
|
telescope: str | None = None,
|
|
78
|
+
roof: str | None = None,
|
|
76
79
|
**kwargs: Any,
|
|
77
80
|
):
|
|
78
81
|
"""Creates a new dummy camera.
|
|
@@ -86,6 +89,10 @@ class DummyCamera(BaseCamera, IWindow, IBinning, ICooling, IGain, IImageFormat):
|
|
|
86
89
|
max_mag: Maximum magnitude of simulated stars.
|
|
87
90
|
seeing: Seeing in arcsec FWHM.
|
|
88
91
|
telescope: Name of telescope module to read pointing from (for star simulation).
|
|
92
|
+
roof: Name of roof module to read open/closed state from. When given, the sky (flat
|
|
93
|
+
flux and stars) is only simulated while the roof is not parked -- frames come
|
|
94
|
+
back dark otherwise. Fails closed: until the roof's state has been received, the
|
|
95
|
+
roof is assumed closed.
|
|
89
96
|
"""
|
|
90
97
|
BaseCamera.__init__(self, **kwargs)
|
|
91
98
|
self.add_background_task(self._cooling_thread, True)
|
|
@@ -112,6 +119,10 @@ class DummyCamera(BaseCamera, IWindow, IBinning, ICooling, IGain, IImageFormat):
|
|
|
112
119
|
self._telescope_module = telescope
|
|
113
120
|
self._telescope_pos: SkyCoord = SkyCoord(0.0 * u.deg, 0.0 * u.deg, frame="icrs")
|
|
114
121
|
|
|
122
|
+
# roof state (updated via subscribe_state); fail closed until a state arrives
|
|
123
|
+
self._roof_module = roof
|
|
124
|
+
self._roof_open: bool = roof is None
|
|
125
|
+
|
|
115
126
|
# camera state
|
|
116
127
|
self._cooling = CoolingStatus()
|
|
117
128
|
self._exposing = True
|
|
@@ -128,6 +139,10 @@ class DummyCamera(BaseCamera, IWindow, IBinning, ICooling, IGain, IImageFormat):
|
|
|
128
139
|
"""Update cached telescope position from IPointingRaDec state."""
|
|
129
140
|
self._telescope_pos = SkyCoord(ra=state.ra * u.deg, dec=state.dec * u.deg, frame="icrs")
|
|
130
141
|
|
|
142
|
+
def _on_roof_state(self, state: MotionState) -> None:
|
|
143
|
+
"""Update cached roof open/closed state from IMotion state."""
|
|
144
|
+
self._roof_open = state.status != MotionStatus.PARKED
|
|
145
|
+
|
|
131
146
|
async def open(self) -> None:
|
|
132
147
|
"""Opens camera."""
|
|
133
148
|
# publish capabilities before super().open()
|
|
@@ -154,6 +169,10 @@ class DummyCamera(BaseCamera, IWindow, IBinning, ICooling, IGain, IImageFormat):
|
|
|
154
169
|
if self._telescope_module:
|
|
155
170
|
await self.comm.subscribe_state(self._telescope_module, IPointingRaDec, self._on_telescope_state)
|
|
156
171
|
|
|
172
|
+
# subscribe to roof state if given
|
|
173
|
+
if self._roof_module:
|
|
174
|
+
await self.comm.subscribe_state(self._roof_module, IMotion, self._on_roof_state)
|
|
175
|
+
|
|
157
176
|
# publish initial states
|
|
158
177
|
await self.comm.set_state(
|
|
159
178
|
ICooling,
|
|
@@ -206,7 +225,7 @@ class DummyCamera(BaseCamera, IWindow, IBinning, ICooling, IGain, IImageFormat):
|
|
|
206
225
|
|
|
207
226
|
if exp_time > 0:
|
|
208
227
|
data += make_noise_image(shape, distribution="gaussian", mean=exp_time / 1e4, stddev=exp_time / 1e5)
|
|
209
|
-
if open_shutter:
|
|
228
|
+
if open_shutter and self._roof_open:
|
|
210
229
|
sun_alt = self._sun_alt()
|
|
211
230
|
flat_counts = 30000 / np.exp(-1.28 * (4.209 + sun_alt)) * exp_time
|
|
212
231
|
data += make_noise_image(shape, distribution="gaussian", mean=flat_counts, stddev=flat_counts / 10.0)
|
|
@@ -158,7 +158,7 @@ class AutoFocusSeries(Module, CameraSettingsMixin, IAutoFocus):
|
|
|
158
158
|
foc_state = focuser.get_state(IFocuser)
|
|
159
159
|
guess = foc_state.focus if foc_state is not None else 0.0
|
|
160
160
|
log.info("Using current focus of %.2fmm as initial guess.", guess)
|
|
161
|
-
except exc.PyobsError:
|
|
161
|
+
except (exc.PyobsError, ValueError):
|
|
162
162
|
raise exc.FocusError("Could not fetch current focus value.")
|
|
163
163
|
|
|
164
164
|
# define array of focus values to iterate
|
|
@@ -185,7 +185,7 @@ class AutoFocusSeries(Module, CameraSettingsMixin, IAutoFocus):
|
|
|
185
185
|
else:
|
|
186
186
|
await focuser.set_focus(float(foc))
|
|
187
187
|
|
|
188
|
-
except exc.PyobsError:
|
|
188
|
+
except (exc.PyobsError, ValueError):
|
|
189
189
|
raise exc.FocusError("Could not set new focus value.")
|
|
190
190
|
|
|
191
191
|
# do exposure
|
|
@@ -5,7 +5,7 @@ import os
|
|
|
5
5
|
import time
|
|
6
6
|
import warnings
|
|
7
7
|
from dataclasses import dataclass
|
|
8
|
-
from pathlib import PurePosixPath
|
|
8
|
+
from pathlib import Path, PurePosixPath
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
11
|
from astropy.io import fits
|
|
@@ -47,6 +47,7 @@ class ImageWatcher(Module):
|
|
|
47
47
|
poll_interval: int = 5,
|
|
48
48
|
wait_time: int = 10,
|
|
49
49
|
pattern: str = "*",
|
|
50
|
+
flatten: bool = True,
|
|
50
51
|
**kwargs: Any,
|
|
51
52
|
):
|
|
52
53
|
"""Create a new image watcher.
|
|
@@ -59,6 +60,26 @@ class ImageWatcher(Module):
|
|
|
59
60
|
wait_time: Time in seconds between adding a file to the list and processing it. Gives a file that is
|
|
60
61
|
still being written time to finish (relevant for poll mode and the initial scan) and spaces out
|
|
61
62
|
re-queued files after failed destination copies.
|
|
63
|
+
pattern: Only watch/process files matching this fnmatch pattern, checked against the full path (so
|
|
64
|
+
e.g. "*.fits" also matches files found in subdirectories). This is the single point of filtering,
|
|
65
|
+
applied uniformly in ``add_file`` regardless of watch mode -- inotify events, polling, and the
|
|
66
|
+
initial scan in ``open()`` all discover candidates first and let ``add_file`` decide, none of
|
|
67
|
+
them pre-filter by pattern before that.
|
|
68
|
+
flatten: For a non-templated destination (one without ``{placeholder}``s), whether to collapse the
|
|
69
|
+
file's path to just its basename under that destination (the historical behavior, default) or to
|
|
70
|
+
preserve its path relative to ``watchpath`` under the destination instead. Files found via
|
|
71
|
+
recursive watching or polling only keep their subdirectory structure at the destination when this
|
|
72
|
+
is False. Note this relies on the destination VFS root creating missing parent directories on
|
|
73
|
+
write (the default for e.g. ``LocalFile``) -- a destination configured to not do that would
|
|
74
|
+
repeatedly fail and re-queue any file whose relative path needs a parent directory that doesn't
|
|
75
|
+
exist yet.
|
|
76
|
+
|
|
77
|
+
Note:
|
|
78
|
+
If a directory holding a file that's already queued (added but not yet processed by ``_worker``) is
|
|
79
|
+
renamed before that happens, the worker's read of the file at its captured (pre-rename) path fails
|
|
80
|
+
and the file is dropped rather than picked up at its new location -- this is unrelated to, and not
|
|
81
|
+
covered by, the recursive-watching support for directories renamed after they no longer hold queued
|
|
82
|
+
files. Avoid renaming directories that may still hold queued/retrying files.
|
|
62
83
|
"""
|
|
63
84
|
Module.__init__(self, **kwargs)
|
|
64
85
|
|
|
@@ -77,6 +98,7 @@ class ImageWatcher(Module):
|
|
|
77
98
|
self._poll_interval = poll_interval
|
|
78
99
|
self._wait_time = wait_time
|
|
79
100
|
self._pattern = pattern
|
|
101
|
+
self._flatten = flatten
|
|
80
102
|
self.current_file: CurrentFile | None = None
|
|
81
103
|
|
|
82
104
|
# filename patterns
|
|
@@ -85,49 +107,58 @@ class ImageWatcher(Module):
|
|
|
85
107
|
self._destinations = destinations
|
|
86
108
|
|
|
87
109
|
async def _watch_inotify(self) -> None:
|
|
88
|
-
from asyncinotify import
|
|
110
|
+
from asyncinotify import Mask, RecursiveWatcher
|
|
89
111
|
|
|
90
112
|
# get local directory
|
|
91
113
|
local = await self.vfs.local_path(self._watchpath)
|
|
92
114
|
|
|
93
|
-
#
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
115
|
+
# recursively watch the local directory: watches are added for subdirectories as they're
|
|
116
|
+
# created or moved in, and removed as they're moved out (deletion needs no explicit
|
|
117
|
+
# removal -- the kernel invalidates the watch on its own, see the plan doc for details).
|
|
118
|
+
# A directory can also vanish between an event being received and asyncinotify acting on
|
|
119
|
+
# it (its own add_watch/rm_watch racing a concurrent delete), which raises OSError from
|
|
120
|
+
# inside the library rather than being handled there. Restart with a fresh watcher (a
|
|
121
|
+
# full re-walk) on that instead of letting it kill this task outright -- self-heals faster
|
|
122
|
+
# than falling back to the module's outer background-task restart-with-backoff.
|
|
123
|
+
while True:
|
|
124
|
+
try:
|
|
125
|
+
watcher = RecursiveWatcher(Path(local), Mask.CLOSE_WRITE)
|
|
126
|
+
async for event in watcher.watch_recursive():
|
|
127
|
+
# get filename by replacing local with watchpath
|
|
128
|
+
filename = str(event.path).replace(local, self._watchpath)
|
|
102
129
|
|
|
103
|
-
|
|
104
|
-
|
|
130
|
+
# add file
|
|
131
|
+
await self.add_file(filename)
|
|
132
|
+
except OSError as e:
|
|
133
|
+
log.warning("Inotify watch error (directory likely vanished mid-scan), restarting watch: %s", e)
|
|
105
134
|
|
|
106
135
|
async def _watch_poll(self) -> None:
|
|
107
|
-
# init list
|
|
108
|
-
|
|
136
|
+
# init list (recursive, so subdirectories are picked up too; unfiltered by pattern here --
|
|
137
|
+
# add_file is the single point of pattern filtering, same as inotify mode)
|
|
138
|
+
files = set(await self.vfs.find(self._watchpath, "*"))
|
|
109
139
|
|
|
110
140
|
# run forever
|
|
111
|
-
path = PurePosixPath(self._watchpath)
|
|
112
141
|
while True:
|
|
142
|
+
await asyncio.sleep(self._poll_interval)
|
|
143
|
+
|
|
113
144
|
# get new list
|
|
114
|
-
new_files = await self.vfs.
|
|
145
|
+
new_files = set(await self.vfs.find(self._watchpath, "*"))
|
|
115
146
|
|
|
116
147
|
# find all new files and add them
|
|
117
|
-
for f in new_files:
|
|
118
|
-
|
|
119
|
-
print(str(path / f))
|
|
120
|
-
await self.add_file(str(path / f))
|
|
148
|
+
for f in new_files - files:
|
|
149
|
+
await self.add_file(os.path.join(self._watchpath, f))
|
|
121
150
|
|
|
122
151
|
# store new list
|
|
123
|
-
files =
|
|
152
|
+
files = new_files
|
|
124
153
|
|
|
125
154
|
async def open(self) -> None:
|
|
126
155
|
"""Open image watcher."""
|
|
127
156
|
await Module.open(self)
|
|
128
157
|
|
|
129
|
-
# add all files from directory to queue
|
|
130
|
-
|
|
158
|
+
# add all files from directory to queue (recursive, so subdirectories are picked up too;
|
|
159
|
+
# unfiltered by pattern here -- add_file is the single point of pattern filtering, same as
|
|
160
|
+
# inotify mode)
|
|
161
|
+
for filename in await self.vfs.find(self._watchpath, "*"):
|
|
131
162
|
await self.add_file(os.path.join(self._watchpath, filename))
|
|
132
163
|
|
|
133
164
|
async def close(self) -> None:
|
|
@@ -193,10 +224,15 @@ class ImageWatcher(Module):
|
|
|
193
224
|
if out_filename is None:
|
|
194
225
|
raise ValueError("Could not create name for file.")
|
|
195
226
|
|
|
196
|
-
|
|
227
|
+
elif self._flatten:
|
|
197
228
|
# no formatting, so just add filename to destination
|
|
198
229
|
out_filename = os.path.join(pattern, os.path.basename(filename))
|
|
199
230
|
|
|
231
|
+
else:
|
|
232
|
+
# preserve the file's path relative to watchpath under the destination
|
|
233
|
+
rel = PurePosixPath(filename).relative_to(self._watchpath)
|
|
234
|
+
out_filename = str(PurePosixPath(pattern) / rel)
|
|
235
|
+
|
|
200
236
|
# store it
|
|
201
237
|
log.info("Storing file as %s...", out_filename)
|
|
202
238
|
self.current_file.out_filename = out_filename
|
|
@@ -439,8 +439,8 @@ class Module(Object, IModule, IConfig):
|
|
|
439
439
|
caps = proxy.get_capabilities(IModule)
|
|
440
440
|
module_version = caps.version if caps is not None else ""
|
|
441
441
|
remote_location = caps.location if caps is not None else None
|
|
442
|
-
except exc.PyobsError:
|
|
443
|
-
# however this failed (transport or
|
|
442
|
+
except (exc.PyobsError, ValueError):
|
|
443
|
+
# however this failed (transport, domain, or unresolvable peer), we just skip this module
|
|
444
444
|
return True
|
|
445
445
|
|
|
446
446
|
# log it
|
|
@@ -180,23 +180,38 @@ class Scheduler(Module, IRunnable, IRoboticScheduler):
|
|
|
180
180
|
)
|
|
181
181
|
log.info("Downloaded %d schedulable tasks(s).", len(tasks))
|
|
182
182
|
|
|
183
|
-
# download projects
|
|
184
|
-
self.
|
|
185
|
-
|
|
186
|
-
|
|
183
|
+
# download projects -- keep the old list around long enough to diff against below,
|
|
184
|
+
# mirroring self._tasks (only overwritten once _need_update has been decided)
|
|
185
|
+
old_projects = self._projects
|
|
186
|
+
projects = await self._task_archive.get_projects()
|
|
187
|
+
# `updated_at` is excluded: it's a portal-side save timestamp (round-trip-only field, not
|
|
188
|
+
# scheduling-relevant), and a no-op re-save still bumps it -- comparing it in would force
|
|
189
|
+
# a reschedule on every no-op edit, not just real content changes.
|
|
190
|
+
projects_changed = {p.code: p.model_dump(exclude={"updated_at"}) for p in projects} != {
|
|
191
|
+
p.code: p.model_dump(exclude={"updated_at"}) for p in old_projects
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
# compare new and old lists, both by ID (removed/added) and by content (changed, same ID)
|
|
187
195
|
removed, added = self._compare_task_lists(self._tasks, tasks)
|
|
196
|
+
changed = self._changed_task_ids(self._tasks, tasks)
|
|
188
197
|
|
|
189
198
|
# schedule update
|
|
190
199
|
self._need_update = True
|
|
191
200
|
|
|
192
|
-
# no changes?
|
|
193
|
-
if
|
|
201
|
+
# no changes at all?
|
|
202
|
+
if (
|
|
203
|
+
not self._trigger_on_every_update
|
|
204
|
+
and len(removed) == 0
|
|
205
|
+
and len(added) == 0
|
|
206
|
+
and len(changed) == 0
|
|
207
|
+
and not projects_changed
|
|
208
|
+
):
|
|
194
209
|
# no need to re-schedule
|
|
195
210
|
log.info("No change in list of blocks detected.")
|
|
196
211
|
self._need_update = False
|
|
197
212
|
|
|
198
|
-
# has only the current block been removed?
|
|
199
|
-
log.info("Removed: %d, added: %d", len(removed), len(added))
|
|
213
|
+
# has only the current block been removed, with nothing else changed?
|
|
214
|
+
log.info("Removed: %d, added: %d, changed: %d", len(removed), len(added), len(changed))
|
|
200
215
|
if len(removed) == 1:
|
|
201
216
|
log.info(
|
|
202
217
|
"Found 1 removed block with ID %d. Last task ID was %s, current is %s.",
|
|
@@ -204,21 +219,20 @@ class Scheduler(Module, IRunnable, IRoboticScheduler):
|
|
|
204
219
|
str(self._last_task_id),
|
|
205
220
|
str(self._current_task_id),
|
|
206
221
|
)
|
|
207
|
-
if
|
|
222
|
+
if (
|
|
223
|
+
len(removed) == 1
|
|
224
|
+
and len(added) == 0
|
|
225
|
+
and len(changed) == 0
|
|
226
|
+
and not projects_changed
|
|
227
|
+
and removed[0] == self._last_task_id
|
|
228
|
+
):
|
|
208
229
|
# no need to re-schedule
|
|
209
230
|
log.info("Only one removed block detected, which is the one currently running.")
|
|
210
231
|
self._need_update = False
|
|
211
232
|
|
|
212
|
-
# check, if one of the removed blocks was actually in schedule
|
|
213
|
-
if len(removed) > 0 and self._need_update:
|
|
214
|
-
schedule = await self._schedule.get_schedule()
|
|
215
|
-
removed_from_schedule = [s for s in schedule if s.task.id in removed]
|
|
216
|
-
if len(removed_from_schedule) == 0:
|
|
217
|
-
log.info("Found %s tasks, but none of them was scheduled.", len(removed))
|
|
218
|
-
self._need_update = False
|
|
219
|
-
|
|
220
233
|
# store blocks
|
|
221
234
|
self._tasks = tasks
|
|
235
|
+
self._projects = projects
|
|
222
236
|
|
|
223
237
|
# schedule update
|
|
224
238
|
if self._need_update:
|
|
@@ -252,6 +266,33 @@ class Scheduler(Module, IRunnable, IRoboticScheduler):
|
|
|
252
266
|
|
|
253
267
|
return sorted(additional1), sorted(additional2) # type: ignore[type-var]
|
|
254
268
|
|
|
269
|
+
@staticmethod
|
|
270
|
+
def _content_dump(task: Task) -> dict[str, Any]:
|
|
271
|
+
"""``task.model_dump(exclude={"updated_at"})``.
|
|
272
|
+
|
|
273
|
+
``updated_at`` is a portal-side save timestamp, not scheduling-relevant content, and a
|
|
274
|
+
no-op re-save (e.g. an unchanged DRF PATCH) still bumps it -- leaving it in would report
|
|
275
|
+
every no-op edit as a "changed" task.
|
|
276
|
+
"""
|
|
277
|
+
return task.model_dump(exclude={"updated_at"})
|
|
278
|
+
|
|
279
|
+
@classmethod
|
|
280
|
+
def _changed_task_ids(cls, tasks1: list[Task], tasks2: list[Task]) -> set[Any]:
|
|
281
|
+
"""IDs present in both lists whose task content differs between them.
|
|
282
|
+
|
|
283
|
+
Comparison is via ``model_dump()`` (see :meth:`_content_dump`), not pydantic ``==``:
|
|
284
|
+
``Task.__eq__`` also compares runtime-only ``PrivateAttr``s (e.g. ``_cant_run_reason`` set
|
|
285
|
+
by ``can_run()``, or ``_resolved_target`` set while scheduling), which would make a task
|
|
286
|
+
that has merely been scheduled/run look "changed" on the next poll. ``model_dump()``
|
|
287
|
+
excludes private attributes by construction. What's left after also dropping
|
|
288
|
+
``updated_at`` (priority, duration, script, ...) are the fields that actually matter for
|
|
289
|
+
scheduling.
|
|
290
|
+
"""
|
|
291
|
+
by_id1 = {t.id: t for t in tasks1}
|
|
292
|
+
by_id2 = {t.id: t for t in tasks2}
|
|
293
|
+
common = set(by_id1).intersection(by_id2)
|
|
294
|
+
return {i for i in common if cls._content_dump(by_id1[i]) != cls._content_dump(by_id2[i])}
|
|
295
|
+
|
|
255
296
|
async def _schedule_worker(self) -> None:
|
|
256
297
|
await asyncio.sleep(5)
|
|
257
298
|
|
|
@@ -122,7 +122,7 @@ class DummyRoof(BaseRoof, IRoof):
|
|
|
122
122
|
await self._change_motion_status(MotionStatus.ABORTING)
|
|
123
123
|
|
|
124
124
|
async with LockWithAbort(self._lock_motion, self._abort_motion):
|
|
125
|
-
await self._change_motion_status(MotionStatus.IDLE)
|
|
125
|
+
await self._change_motion_status(MotionStatus.PARKED if self._is_closed() else MotionStatus.IDLE)
|
|
126
126
|
|
|
127
127
|
|
|
128
128
|
__all__ = ["DummyRoof"]
|
|
@@ -78,9 +78,12 @@ class MissingObserverError(exc.MotionError):
|
|
|
78
78
|
pass
|
|
79
79
|
|
|
80
80
|
|
|
81
|
-
class AltitudeLimitError(exc.
|
|
81
|
+
class AltitudeLimitError(exc.PyobsError):
|
|
82
82
|
"""Destination altitude is below the configured limit -- an expected, deferrable condition
|
|
83
|
-
from a scheduler's point of view, not a failure to alert on.
|
|
83
|
+
from a scheduler's point of view, not a failure to alert on. Deliberately not a MotionError:
|
|
84
|
+
it's not a motion fault (nothing was attempted), so it must not count towards the module's
|
|
85
|
+
repeated-motion-failure escalation (see _register_exception(exc.MotionError, ...) below) --
|
|
86
|
+
picking a target below the horizon is normal, not a sign the telescope is broken."""
|
|
84
87
|
|
|
85
88
|
pass
|
|
86
89
|
|
{pyobs_core-2.2.0 → pyobs_core-2.3.0}/pyobs/robotic/storage/filesystem/observationarchive.py
RENAMED
|
@@ -248,6 +248,12 @@ class YamlObservationArchive(FileSystemObservationArchive):
|
|
|
248
248
|
return ObservationList([self.pyobs_model_validate(Observation, obs) for obs in observations])
|
|
249
249
|
|
|
250
250
|
async def _save_observations_to_file(self, path: str, observations: ObservationList) -> None:
|
|
251
|
+
# exclude_defaults now also applies to the nested (PolymorphicBaseModel) Task/Constraint/
|
|
252
|
+
# etc. fields, not just Observation's own -- pyobs-core#855 fixed inject_class_on_
|
|
253
|
+
# serialization() silently ignoring it. Saved YAML files drop more default-valued task
|
|
254
|
+
# fields (active, empty constraints/merits/script, priority: 1.0, ...) than before; this
|
|
255
|
+
# is intentional and round-trips fine (model_validate refills the defaults), but anyone
|
|
256
|
+
# reading these files raw will notice the shape change.
|
|
251
257
|
data = [obs.model_dump(mode="json", exclude_defaults=True) for obs in observations]
|
|
252
258
|
with open(path, "w") as f:
|
|
253
259
|
yaml.safe_dump(data, f)
|
|
@@ -37,6 +37,9 @@ class PortalObservationArchive(ObservationArchive):
|
|
|
37
37
|
# First failure logs immediately at ERROR (someone should know right away); repeats
|
|
38
38
|
# during the same outage are throttled to at most one ERROR per minute.
|
|
39
39
|
self._poll_error_throttle = LogThrottle(quiet_for=0.0, interval=60.0)
|
|
40
|
+
# Same pattern, keyed per observation id: a portal outage would otherwise re-log an
|
|
41
|
+
# ERROR every poll for as long as the cancel PUT keeps failing.
|
|
42
|
+
self._cancel_error_throttle = LogThrottle(quiet_for=0.0, interval=60.0)
|
|
40
43
|
|
|
41
44
|
if auto_update:
|
|
42
45
|
self.add_background_task(self._check_for_changes)
|
|
@@ -171,6 +174,39 @@ class PortalObservationArchive(ObservationArchive):
|
|
|
171
174
|
"""
|
|
172
175
|
return self._observations
|
|
173
176
|
|
|
177
|
+
async def _resolve_or_cancel_pending(self, obs: Observation, task_archive: TaskArchive) -> bool:
|
|
178
|
+
"""Resolve a *pending* obs.task; if the task archive has completed at least one poll and
|
|
179
|
+
the portal no longer has the task, mark obs canceled so it stops being retried on every
|
|
180
|
+
subsequent poll instead of logged-and-skipped forever.
|
|
181
|
+
|
|
182
|
+
Only for pending observations: an in-progress one should be allowed to finish even if its
|
|
183
|
+
task was deactivated mid-run, not flipped to canceled out from under the mastermind.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
True if obs.task resolved and obs is usable.
|
|
187
|
+
"""
|
|
188
|
+
task_id = obs.task
|
|
189
|
+
await obs.fetch_task(task_archive)
|
|
190
|
+
if obs.task is not None:
|
|
191
|
+
return True
|
|
192
|
+
if await task_archive.last_changed() is None:
|
|
193
|
+
# task archive has never completed a poll (e.g. still starting up) -- a genuinely
|
|
194
|
+
# missing task looks the same as this transient race, so don't cancel on it.
|
|
195
|
+
log.error("Could not resolve task for observation %s, skipping.", obs.id)
|
|
196
|
+
return False
|
|
197
|
+
log.error("Could not resolve task for observation %s, marking canceled.", obs.id)
|
|
198
|
+
obs.task = task_id # portal's task FK is non-nullable -- keep the id for the PUT payload
|
|
199
|
+
obs.state = ObservationState.CANCELED
|
|
200
|
+
try:
|
|
201
|
+
await self.update_observation(obs)
|
|
202
|
+
self._cancel_error_throttle.clear(str(obs.id))
|
|
203
|
+
except Exception as e:
|
|
204
|
+
if self._cancel_error_throttle.should_escalate(str(obs.id)):
|
|
205
|
+
log.error("Failed to mark observation %s canceled on portal: %s", obs.id, e)
|
|
206
|
+
else:
|
|
207
|
+
log.debug("Failed to mark observation %s canceled on portal: %s", obs.id, e)
|
|
208
|
+
return False
|
|
209
|
+
|
|
174
210
|
async def get_next_observation(self, time: Time, task_archive: TaskArchive | None = None) -> Observation | None:
|
|
175
211
|
"""Returns the active scheduled task at the given time.
|
|
176
212
|
|
|
@@ -183,11 +219,8 @@ class PortalObservationArchive(ObservationArchive):
|
|
|
183
219
|
"""
|
|
184
220
|
for obs in self._observations:
|
|
185
221
|
if obs.state == "pending" and obs.start < time < obs.end:
|
|
186
|
-
if task_archive is not None:
|
|
187
|
-
|
|
188
|
-
if obs.task is None:
|
|
189
|
-
log.error("Could not resolve task for observation %s, skipping.", obs.id)
|
|
190
|
-
continue
|
|
222
|
+
if task_archive is not None and not await self._resolve_or_cancel_pending(obs, task_archive):
|
|
223
|
+
continue
|
|
191
224
|
return obs
|
|
192
225
|
else:
|
|
193
226
|
return None
|
|
@@ -92,13 +92,17 @@ class PortalTaskArchive(TaskArchive):
|
|
|
92
92
|
``model_dump()`` rather than pydantic ``==``, which also compares runtime attributes (e.g.
|
|
93
93
|
``Task._cant_run_reason`` set by ``can_run()``) and would flag unchanged tasks as changed
|
|
94
94
|
on every poll; it is keyed by ID so that a stable reordering of the same items (e.g. an
|
|
95
|
-
unordered portal queryset) is not mistaken for a change.
|
|
95
|
+
unordered portal queryset) is not mistaken for a change. ``updated_at`` is excluded from
|
|
96
|
+
both comparisons via ``exclude=`` so a no-op re-save doesn't trigger a re-download/
|
|
97
|
+
reschedule cascade (see pyobs-core#856).
|
|
96
98
|
"""
|
|
97
99
|
projects = await self._get_projects()
|
|
98
100
|
tasks = await self._get_tasks()
|
|
99
|
-
if {p.code: p.model_dump() for p in projects} != {
|
|
100
|
-
|
|
101
|
-
}
|
|
101
|
+
if {p.code: p.model_dump(exclude={"updated_at"}) for p in projects} != {
|
|
102
|
+
p.code: p.model_dump(exclude={"updated_at"}) for p in self._projects
|
|
103
|
+
} or {t.id: t.model_dump(exclude={"updated_at"}) for t in tasks} != {
|
|
104
|
+
t.id: t.model_dump(exclude={"updated_at"}) for t in self._tasks
|
|
105
|
+
}:
|
|
102
106
|
self._projects = projects
|
|
103
107
|
self._tasks = tasks
|
|
104
108
|
self._last_update = Time.now()
|
|
@@ -155,6 +155,10 @@ class Project(BaseModel):
|
|
|
155
155
|
priority: float | None = Field(ge=0.0, le=9999.0, default=1.0)
|
|
156
156
|
users: list[str] = Field(default_factory=list)
|
|
157
157
|
public: bool = False # ingest backend `public` flag; default keeps old backends valid
|
|
158
|
+
updated_at: str | None = None
|
|
159
|
+
"""Last-modification timestamp as emitted by pyobs-portal (DB-derived ``updated_at``,
|
|
160
|
+
added in pyobs-portal#134). Carried for round-trip fidelity so the strict ``Project`` model
|
|
161
|
+
(``extra="forbid"``) accepts the field the portal now sends; not used by pyobs itself."""
|
|
158
162
|
|
|
159
163
|
|
|
160
164
|
__all__ = ["Task", "TaskData", "Project"]
|
|
@@ -44,6 +44,7 @@ class Reduction(ReductionBase):
|
|
|
44
44
|
calib_science: bool = True,
|
|
45
45
|
progress_callback: ProgressCallback | None = None,
|
|
46
46
|
dark_exptime_tolerance: float = 0.01,
|
|
47
|
+
min_darks_per_group: int = 3,
|
|
47
48
|
):
|
|
48
49
|
"""Creates a Reduction object for reducing a given observation period.
|
|
49
50
|
|
|
@@ -60,6 +61,8 @@ class Reduction(ReductionBase):
|
|
|
60
61
|
progress_callback: See ReductionBase.
|
|
61
62
|
dark_exptime_tolerance: Relative tolerance for grouping a night's raw DARK frames
|
|
62
63
|
into per-exptime master series -- see pyobs.utils.exptime_grouping.
|
|
64
|
+
min_darks_per_group: Minimum number of raw frames in a per-exptime DARK group to
|
|
65
|
+
combine a master dark from; smaller groups are skipped individually.
|
|
63
66
|
"""
|
|
64
67
|
super().__init__(archive=archive, pipeline=pipeline, min_flats=min_flats, progress_callback=progress_callback)
|
|
65
68
|
|
|
@@ -70,6 +73,7 @@ class Reduction(ReductionBase):
|
|
|
70
73
|
self._create_calibs = create_calibs
|
|
71
74
|
self._calib_science = calib_science
|
|
72
75
|
self._dark_exptime_tolerance = dark_exptime_tolerance
|
|
76
|
+
self._min_darks_per_group = min_darks_per_group
|
|
73
77
|
|
|
74
78
|
# make sure the local output directory exists
|
|
75
79
|
if self._store_local:
|
|
@@ -195,8 +199,9 @@ class Reduction(ReductionBase):
|
|
|
195
199
|
"""Creates one master dark per exposure time present in the night's raw DARK frames
|
|
196
200
|
(grouped within self._dark_exptime_tolerance) -- a night can have darks at more than
|
|
197
201
|
one exptime now that DarkBiasScript can match them to the night's science exptimes
|
|
198
|
-
(#831). Frame groups with fewer than
|
|
199
|
-
warning, rather than aborting dark reduction for this
|
|
202
|
+
(#831). Frame groups with fewer than self._min_darks_per_group members are skipped
|
|
203
|
+
individually with a warning, rather than aborting dark reduction for this
|
|
204
|
+
instrument/binning entirely.
|
|
200
205
|
|
|
201
206
|
If none of the night's raw darks carry EXPTIME at all (a fully legacy instrument, as
|
|
202
207
|
opposed to a handful of untagged frames mixed in with tagged ones), grouping would
|
|
@@ -247,12 +252,12 @@ class Reduction(ReductionBase):
|
|
|
247
252
|
for group, exptime in groups:
|
|
248
253
|
label = "legacy (no exptime)" if exptime is None else f"~{exptime}s"
|
|
249
254
|
|
|
250
|
-
if len(group) <
|
|
255
|
+
if len(group) < self._min_darks_per_group:
|
|
251
256
|
log.warning("Too few (%d) dark frames at %s, skipping...", len(group), label)
|
|
252
257
|
continue
|
|
253
258
|
|
|
254
259
|
images = await self._archive.download_frames(group)
|
|
255
|
-
if len(images) <
|
|
260
|
+
if len(images) < self._min_darks_per_group:
|
|
256
261
|
log.warning("Too few (%d) dark frames at %s, skipping...", len(images), label)
|
|
257
262
|
continue
|
|
258
263
|
|
|
@@ -7,7 +7,7 @@ from typing import Any, Self, TypeVar
|
|
|
7
7
|
from astroplan import Observer
|
|
8
8
|
from pydantic import BaseModel as PydanticBaseModel
|
|
9
9
|
from pydantic import ConfigDict, PrivateAttr, model_serializer, model_validator
|
|
10
|
-
from pydantic_core.core_schema import ValidationInfo, ValidatorFunctionWrapHandler
|
|
10
|
+
from pydantic_core.core_schema import SerializationInfo, ValidationInfo, ValidatorFunctionWrapHandler
|
|
11
11
|
|
|
12
12
|
from pyobs.comm import Comm
|
|
13
13
|
from pyobs.object import PrivateAttrMixin
|
|
@@ -40,11 +40,57 @@ class BaseModel(PydanticBaseModel, PrivateAttrMixin):
|
|
|
40
40
|
class PolymorphicBaseModel(BaseModel, metaclass=ABCMeta): # type: ignore[misc]
|
|
41
41
|
"""Pydantic base model for pyobs sub classes that need to be serialized."""
|
|
42
42
|
|
|
43
|
+
def _flat_field_spec(self, spec: Any, info_kwarg: str) -> set[str] | None:
|
|
44
|
+
"""Reduce a pydantic `exclude=`/`include=` spec to a flat set of field names.
|
|
45
|
+
|
|
46
|
+
`inject_class_on_serialization` hand-builds its output dict instead of delegating to
|
|
47
|
+
`handler`, so it also has to hand-apply `exclude`/`include` itself -- pydantic's spec
|
|
48
|
+
grammar supports nested dicts (partial excludes on sub-fields) and integer/`__all__` keys
|
|
49
|
+
(per-element excludes on sequences), forwarded into each field's own serialization. Doing
|
|
50
|
+
that generically would mean reimplementing a chunk of pydantic-core's own traversal; since
|
|
51
|
+
every current caller only ever passes a flat set of top-level field names (see
|
|
52
|
+
pyobs-core#855), only that flat form is supported. Anything else raises rather than
|
|
53
|
+
silently ignoring or partially applying the spec.
|
|
54
|
+
"""
|
|
55
|
+
if spec is None:
|
|
56
|
+
return None
|
|
57
|
+
if isinstance(spec, set | frozenset):
|
|
58
|
+
return set(spec)
|
|
59
|
+
if isinstance(spec, dict) and all(v is True for v in spec.values()):
|
|
60
|
+
return set(spec.keys())
|
|
61
|
+
raise NotImplementedError(
|
|
62
|
+
f"PolymorphicBaseModel.model_dump() only supports a flat field-name {info_kwarg}= "
|
|
63
|
+
f"(no nested specs) -- got {spec!r} for {type(self).__name__}; see pyobs-core#855"
|
|
64
|
+
)
|
|
65
|
+
|
|
43
66
|
@model_serializer(mode="wrap")
|
|
44
|
-
def inject_class_on_serialization(
|
|
67
|
+
def inject_class_on_serialization(
|
|
68
|
+
self, handler: ValidatorFunctionWrapHandler, info: SerializationInfo
|
|
69
|
+
) -> dict[str, Any]:
|
|
45
70
|
# Collect fields from the concrete runtime type to avoid Pydantic v2
|
|
46
71
|
# resolving field schemas against the abstract base type when nested in a parent model
|
|
47
|
-
|
|
72
|
+
if info.exclude_computed_fields and type(self).__pydantic_decorators__.computed_fields:
|
|
73
|
+
raise NotImplementedError(
|
|
74
|
+
f"PolymorphicBaseModel.model_dump() doesn't support exclude_computed_fields= for "
|
|
75
|
+
f"{type(self).__name__}; see pyobs-core#855"
|
|
76
|
+
)
|
|
77
|
+
exclude = self._flat_field_spec(info.exclude, "exclude")
|
|
78
|
+
include = self._flat_field_spec(info.include, "include")
|
|
79
|
+
fields = type(self).model_fields
|
|
80
|
+
names = [n for n in fields if (include is None or n in include) and (exclude is None or n not in exclude)]
|
|
81
|
+
|
|
82
|
+
result: dict[str, Any] = {}
|
|
83
|
+
for name in names:
|
|
84
|
+
value = getattr(self, name)
|
|
85
|
+
if info.exclude_none and value is None:
|
|
86
|
+
continue
|
|
87
|
+
if info.exclude_unset and name not in self.model_fields_set:
|
|
88
|
+
continue
|
|
89
|
+
if info.exclude_defaults and value == fields[name].get_default(call_default_factory=True):
|
|
90
|
+
continue
|
|
91
|
+
alias = fields[name].alias
|
|
92
|
+
key = alias if (info.by_alias and alias) else name
|
|
93
|
+
result[key] = value
|
|
48
94
|
result["class"] = f"{self.__module__}.{self.__class__.__name__}"
|
|
49
95
|
return result
|
|
50
96
|
|
|
File without changes
|