From 291d98649fe4a362d76a8d8cb77eab3cf92840a4 Mon Sep 17 00:00:00 2001 From: GermanHydrogen Date: Thu, 21 Sep 2023 18:55:41 +0200 Subject: [PATCH 01/22] Added guiding statistics for guiding loop state --- pyobs/modules/pointing/_baseguiding.py | 168 +++++++++++++----- .../pointing/test_guiding_statistic_uptime.py | 26 +++ .../pointing/test_guiding_statistics.py | 40 ----- .../test_guiding_statistics_pixel_offset.py | 41 +++++ 4 files changed, 187 insertions(+), 88 deletions(-) create mode 100644 tests/modules/pointing/test_guiding_statistic_uptime.py delete mode 100644 tests/modules/pointing/test_guiding_statistics.py create mode 100644 tests/modules/pointing/test_guiding_statistics_pixel_offset.py diff --git a/pyobs/modules/pointing/_baseguiding.py b/pyobs/modules/pointing/_baseguiding.py index 2e4e431e..21bdcaef 100644 --- a/pyobs/modules/pointing/_baseguiding.py +++ b/pyobs/modules/pointing/_baseguiding.py @@ -1,15 +1,16 @@ -from abc import ABCMeta +import logging +from abc import ABCMeta, abstractmethod, ABC from collections import defaultdict +from datetime import datetime from typing import Union, List, Dict, Tuple, Any, Optional -import logging +import astropy.units as u import numpy as np from astropy.coordinates import SkyCoord -import astropy.units as u -from pyobs.utils.time import Time -from pyobs.interfaces import IAutoGuiding, IFitsHeaderBefore, IFitsHeaderAfter from pyobs.images import Image +from pyobs.interfaces import IAutoGuiding, IFitsHeaderBefore, IFitsHeaderAfter +from pyobs.utils.time import Time from ._base import BasePointing from ...images.meta import PixelOffsets from ...interfaces import ITelescope @@ -17,25 +18,61 @@ log = logging.getLogger(__name__) -class _GuidingStatistics: +class _GuidingStatistics(ABC): """Calculates statistics for guiding.""" def __init__(self): - self._sessions: Dict[str, List[Tuple[float, float]]] = defaultdict(list) + self._sessions: Dict[str, List[Any]] = defaultdict(list) - def reset_stats(self, client: str) -> None: + def init_stats(self, client: str) -> None: """ - Initializes a stat measurement session for a client. + Inits a stat measurement session for a client. Args: client: name/id of the client """ - if client in self._sessions: - del self._sessions[client] + self._sessions[client] = [] + @abstractmethod + def _build_header(self, data: Any) -> Dict[str, Tuple[Any, str]]: + raise NotImplementedError + + def add_to_header(self, client: str, header: Dict[str, Tuple[Any, str]]) -> Dict[str, Tuple[Any, str]]: + """ + Add statistics to given header. + + Args: + client: id/name of the client + header: Header dict to add statistics to. + """ + + data = self._sessions.pop(client) + session_header = self._build_header(data) + + return header | session_header + + @abstractmethod + def _get_session_data(self, input_data: Any) -> Any: + raise NotImplementedError + + def add_data(self, input_data: Any) -> None: + """ + Adds data to all client measurement sessions. + Args: + input_data: Image witch metadata + """ + + data = self._get_session_data(input_data) + + for k in self._sessions.keys(): + self._sessions[k].append(data) + + +class _GuidingStatisticsPixelOffset(_GuidingStatistics): @staticmethod def _calc_rms(data: List[Tuple[float, float]]) -> Optional[Tuple[float, float]]: - """Calculates RMS on data. + """ + Calculates RMS of data. Args: data: Data to calculate RMS for. @@ -51,43 +88,71 @@ def _calc_rms(data: List[Tuple[float, float]]) -> Optional[Tuple[float, float]]: rms = np.sqrt(np.sum(np.power(flattened_data, 2), axis=1) / data_len) return tuple(rms) - def add_to_header(self, client: str, header: Dict[str, Any]): - """ "Add statistics to given header. - - Args: - client: id/name of the client - header: Header dict to add statistics to. - """ - - # get dataset - data = self._sessions.pop(client) - - # calculate rms + def _build_header(self, data: List[Tuple[float, float]]) -> Dict[str, Tuple[Any, str]]: + header = {} rms = self._calc_rms(data) + if rms is not None: header["GUIDING RMS1"] = (float(rms[0]), "RMS for guiding on axis 1") header["GUIDING RMS2"] = (float(rms[1]), "RMS for guiding on axis 2") - def add_data(self, image: Image) -> None: + return header + + def _get_session_data(self, data: Image) -> Tuple[float, float]: + if data.has_meta(PixelOffsets): + meta = data.get_meta(PixelOffsets) + primitive = tuple(meta.__dict__.values()) + return primitive + else: + log.warning("Image is missing the necessary meta information!") + raise KeyError("Unknown meta.") + + +class _GuidingStatisticsUptime(_GuidingStatistics): + @staticmethod + def _calc_uptime(states: List[Tuple[bool, datetime]]) -> int: + uptimes: List[int] = [] + for i, entry in enumerate(states): + state, timestamp = entry + # is not closed? + if not state or i + 1 == len(states): + continue + + uptime = (states[i + 1][1] - timestamp).seconds + uptimes.append(uptime) + + return sum(uptimes) + + @staticmethod + def _calc_total_time(states: List[Tuple[bool, datetime]]) -> int: + initial_time = states[0][1] + end_time = states[-1][1] + return (end_time - initial_time).seconds + + @staticmethod + def _calc_uptime_percentage(states: List[Tuple[bool, datetime]]) -> float: + uptime = _GuidingStatisticsUptime._calc_uptime(states) + total_time = _GuidingStatisticsUptime._calc_total_time(states) + """ - Adds metadata from an image to all client measurement sessions. - Args: - image: Image witch metadata + If no time has passed, return 100 if the loop is closed, 0 else. + We have to take the second last value in the state array, since the last value is the stop value. """ + if total_time == 0: + return int(states[-2][0]) * 100.0 - # what kind of offsets do we have? - if image.has_meta(PixelOffsets): - # get data - meta = image.get_meta(PixelOffsets) + return uptime / total_time * 100.0 - # add to all - for k in self._sessions.keys(): - self._sessions[k].append((meta.dx, meta.dy)) + def _build_header(self, data: List[Tuple[bool, datetime]]) -> Dict[str, Tuple[Any, str]]: + now = datetime.now() + data.append((None, now)) - else: - # unsupported - log.warning("Image is missing the necessary meta information!") - raise KeyError("Unknown meta.") + uptime_percentage = self._calc_uptime_percentage(data) + return {"GUIDING UPTIME": (uptime_percentage, "Percentage of exposure time the guiding was closed.")} + + def _get_session_data(self, input_data: bool) -> Tuple[bool, datetime]: + now = datetime.now() + return input_data, now class BaseGuiding(BasePointing, IAutoGuiding, IFitsHeaderBefore, IFitsHeaderAfter, metaclass=ABCMeta): @@ -128,14 +193,15 @@ def __init__( self._pid = pid self._reset_at_focus = reset_at_focus self._reset_at_filter = reset_at_filter - self._loop_closed = False + self._set_loop_state(False) # headers of last and of reference image self._last_header = None self._ref_header = None # stats - self._statistics = _GuidingStatistics() + self._statistics = _GuidingStatisticsPixelOffset() + self._uptime = _GuidingStatisticsUptime() async def start(self, **kwargs: Any) -> None: """Starts/resets auto-guiding.""" @@ -168,7 +234,8 @@ async def get_fits_header_before( """ # reset statistics - self._statistics.reset_stats(kwargs["sender"]) + self._statistics.init_stats(kwargs["sender"]) + self._uptime.init_stats(kwargs["sender"]) # state state = "GUIDING_CLOSED_LOOP" if self._loop_closed else "GUIDING_OPEN_LOOP" @@ -194,6 +261,7 @@ async def get_fits_header_after( # add statistics self._statistics.add_to_header(kwargs["sender"], hdr) + self._uptime.add_to_header(kwargs["sender"], hdr) # finished return hdr @@ -205,7 +273,7 @@ async def _reset_guiding(self, enabled: bool = True, image: Optional[Union[Image image: If given, new reference image. """ self._enabled = enabled - self._loop_closed = False + self._set_loop_state(False) self._ref_header = None if image is None else image.header self._last_header = None if image is None else image.header @@ -215,6 +283,10 @@ async def _reset_guiding(self, enabled: bool = True, image: Optional[Union[Image # if image is given, process it await self.run_pipeline(image) + def _set_loop_state(self, state: bool): + self._uptime.add_data(state) + self._loop_closed = state + async def _process_image(self, image: Image) -> Optional[Image]: """Processes a single image and offsets telescope. @@ -287,7 +359,7 @@ async def _process_image(self, image: Image) -> Optional[Image]: # exposure time too large? if self._max_exposure_time is not None and image.header["EXPTIME"] > self._max_exposure_time: log.warning("Exposure time too large, skipping auto-guiding for now...") - self._loop_closed = False + self._set_loop_state(False) return None # remember header @@ -304,20 +376,20 @@ async def _process_image(self, image: Image) -> Optional[Image]: telescope = await self.proxy(self._telescope, ITelescope) except ValueError: log.error("Given telescope does not exist or is not of correct type.") - self._loop_closed = False + self._set_loop_state(False) return image # apply offsets try: if await self._apply(image, telescope, self.location): - self._loop_closed = True + self._set_loop_state(True) log.info("Finished image.") else: log.info("Could not apply offsets.") - self._loop_closed = False + self._set_loop_state(False) except ValueError as e: log.info("Could not apply offsets: %s", e) - self._loop_closed = False + self._set_loop_state(False) # return image, in case we added important data return image diff --git a/tests/modules/pointing/test_guiding_statistic_uptime.py b/tests/modules/pointing/test_guiding_statistic_uptime.py new file mode 100644 index 00000000..425926d1 --- /dev/null +++ b/tests/modules/pointing/test_guiding_statistic_uptime.py @@ -0,0 +1,26 @@ +import pytest + +from datetime import datetime +from pyobs.modules.pointing._baseguiding import _GuidingStatisticsUptime + + +def test_end_to_end(): + client = "camera" + statistic = _GuidingStatisticsUptime() + + statistic.init_stats(client) + + statistic.add_data(True) + + header = statistic.add_to_header(client, {}) + + assert header["GUIDING UPTIME"] == (100.0, "Percentage of exposure time the guiding was closed.") + + +def test_calc_uptime_percentage(): + states = [ + (True, datetime.fromtimestamp(100)), + (False, datetime.fromtimestamp(110)), + (None, datetime.fromtimestamp(120)), + ] + assert _GuidingStatisticsUptime()._calc_uptime_percentage(states) == 50 diff --git a/tests/modules/pointing/test_guiding_statistics.py b/tests/modules/pointing/test_guiding_statistics.py deleted file mode 100644 index 9c2fa00c..00000000 --- a/tests/modules/pointing/test_guiding_statistics.py +++ /dev/null @@ -1,40 +0,0 @@ -import pytest - -from pyobs.images import Image -from pyobs.images.meta import PixelOffsets, SkyOffsets -from pyobs.modules.pointing._baseguiding import _GuidingStatistics - - -@pytest.fixture() -def mock_meta_image(): - image = Image() - image.set_meta(PixelOffsets(1.0, 1.0)) - return image - - -def test_pixel_offset(mock_meta_image): - gsc = _GuidingStatistics() - gsc.reset_stats("camera") - gsc.add_data(mock_meta_image) - gsc.add_data(mock_meta_image) - data = gsc.get_stats("camera") - - assert data == (1.0, 1.0) - - with pytest.raises(KeyError): - gsc.get_stats("camera") - - -def test_calc_rms(): - gsc = _GuidingStatistics() - assert gsc._calc_rms([]) == () - assert gsc._calc_rms([(1.0, 1.0), (1.0, 7.0)]) == (1.0, 5.0) - - -def test_missing_meta(): - gsc = _GuidingStatistics() - gsc.reset_stats("camera") - img = Image() - - with pytest.raises(KeyError): - gsc.add_data(img) diff --git a/tests/modules/pointing/test_guiding_statistics_pixel_offset.py b/tests/modules/pointing/test_guiding_statistics_pixel_offset.py new file mode 100644 index 00000000..b41cfe2a --- /dev/null +++ b/tests/modules/pointing/test_guiding_statistics_pixel_offset.py @@ -0,0 +1,41 @@ +import pytest + +from pyobs.images import Image +from pyobs.images.meta import PixelOffsets, SkyOffsets +from pyobs.modules.pointing._baseguiding import _GuidingStatisticsPixelOffset + + +@pytest.fixture() +def mock_meta_image(): + image = Image() + image.set_meta(PixelOffsets(1.0, 1.0)) + return image + + +def test_end_to_end(mock_meta_image): + client = "camera" + statistic = _GuidingStatisticsPixelOffset() + + statistic.init_stats(client) + + statistic.add_data(mock_meta_image) + statistic.add_data(mock_meta_image) + statistic.add_data(mock_meta_image) + + header = statistic.add_to_header(client, {}) + + assert header["GUIDING RMS1"] == (1.0, "RMS for guiding on axis 1") + assert header["GUIDING RMS2"] == (1.0, "RMS for guiding on axis 2") + + +def test_build_header_to_few_values(): + gspo = _GuidingStatisticsPixelOffset() + assert gspo._build_header([(1.0, 1.0)]) == {} + + +def test_get_session_data(): + image = Image() + gspo = _GuidingStatisticsPixelOffset() + + with pytest.raises(KeyError): + gspo._get_session_data(image) From 605a178c324580bb3af65279f6b760dcb06a04e5 Mon Sep 17 00:00:00 2001 From: GermanHydrogen Date: Thu, 21 Sep 2023 19:45:13 +0200 Subject: [PATCH 02/22] Fixed uptime session --- pyobs/modules/pointing/_baseguiding.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pyobs/modules/pointing/_baseguiding.py b/pyobs/modules/pointing/_baseguiding.py index 21bdcaef..82114577 100644 --- a/pyobs/modules/pointing/_baseguiding.py +++ b/pyobs/modules/pointing/_baseguiding.py @@ -24,15 +24,20 @@ class _GuidingStatistics(ABC): def __init__(self): self._sessions: Dict[str, List[Any]] = defaultdict(list) - def init_stats(self, client: str) -> None: + def init_stats(self, client: str, default: Any = None) -> None: """ Inits a stat measurement session for a client. Args: client: name/id of the client + default: first entry in session """ + self._sessions[client] = [] + if default is not None: + self._sessions[client].append(self._get_session_data(default)) + @abstractmethod def _build_header(self, data: Any) -> Dict[str, Tuple[Any, str]]: raise NotImplementedError @@ -193,7 +198,7 @@ def __init__( self._pid = pid self._reset_at_focus = reset_at_focus self._reset_at_filter = reset_at_filter - self._set_loop_state(False) + self._loop_closed = False # headers of last and of reference image self._last_header = None @@ -235,7 +240,7 @@ async def get_fits_header_before( # reset statistics self._statistics.init_stats(kwargs["sender"]) - self._uptime.init_stats(kwargs["sender"]) + self._uptime.init_stats(kwargs["sender"], self._loop_closed) # state state = "GUIDING_CLOSED_LOOP" if self._loop_closed else "GUIDING_OPEN_LOOP" @@ -260,8 +265,8 @@ async def get_fits_header_after( hdr = {"AGSTATE": (state, "Autoguider state")} # add statistics - self._statistics.add_to_header(kwargs["sender"], hdr) - self._uptime.add_to_header(kwargs["sender"], hdr) + hdr = self._statistics.add_to_header(kwargs["sender"], hdr) + hdr = self._uptime.add_to_header(kwargs["sender"], hdr) # finished return hdr From 20def02f780fe993f292d1840d9290958ca9cb4e Mon Sep 17 00:00:00 2001 From: GermanHydrogen Date: Thu, 21 Sep 2023 19:45:40 +0200 Subject: [PATCH 03/22] Removed depricated tests --- tests/utils/guiding_stat/__init__.py | 0 .../test_exposure_session_container.py | 40 ------------------- 2 files changed, 40 deletions(-) delete mode 100644 tests/utils/guiding_stat/__init__.py delete mode 100644 tests/utils/guiding_stat/test_exposure_session_container.py diff --git a/tests/utils/guiding_stat/__init__.py b/tests/utils/guiding_stat/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/utils/guiding_stat/test_exposure_session_container.py b/tests/utils/guiding_stat/test_exposure_session_container.py deleted file mode 100644 index 81244dc0..00000000 --- a/tests/utils/guiding_stat/test_exposure_session_container.py +++ /dev/null @@ -1,40 +0,0 @@ -from pyobs.utils.guiding_stat.exposure_session_container import ExposureSessionContainer - - -def test_init(): - esc = ExposureSessionContainer() - assert esc._sessions == dict() - - -def test_init_session_new(): - esc = ExposureSessionContainer() - esc.init_session("camera") - assert esc._sessions["camera"] == [] - - -def test_init_session_existing(): - esc = ExposureSessionContainer() - esc._sessions = {"camera": [(0.0, 0.0), (1.1, 1.1)]} - esc.init_session("camera") - assert esc._sessions["camera"] == [] - - -def test_pop_session(): - esc = ExposureSessionContainer() - data = [(0.0, 0.0), (1.0, 1.0)] - esc._sessions = {"camera": data} - - assert esc.pop_session("camera") == data - assert "camera" not in esc._sessions - - -def test_add_data_to_all(): - esc = ExposureSessionContainer() - data = [(0.0, 0.0)] - esc._sessions = {"camera": data, "another": []} - - esc.add_data_to_all((2.0, 2.0)) - - assert esc._sessions["camera"][0] == (0.0, 0.0) - assert esc._sessions["camera"][1] == (2.0, 2.0) - assert esc._sessions["another"][0] == (2.0, 2.0) From af1cc196176d4b36480be6943219d22b497499e0 Mon Sep 17 00:00:00 2001 From: GermanHydrogen Date: Thu, 21 Sep 2023 19:49:45 +0200 Subject: [PATCH 04/22] Fixed header comment --- pyobs/modules/pointing/_baseguiding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyobs/modules/pointing/_baseguiding.py b/pyobs/modules/pointing/_baseguiding.py index 82114577..f7a1d9dd 100644 --- a/pyobs/modules/pointing/_baseguiding.py +++ b/pyobs/modules/pointing/_baseguiding.py @@ -153,7 +153,7 @@ def _build_header(self, data: List[Tuple[bool, datetime]]) -> Dict[str, Tuple[An data.append((None, now)) uptime_percentage = self._calc_uptime_percentage(data) - return {"GUIDING UPTIME": (uptime_percentage, "Percentage of exposure time the guiding was closed.")} + return {"GUIDING UPTIME": (uptime_percentage, "Time the guiding loop was closed [%]")} def _get_session_data(self, input_data: bool) -> Tuple[bool, datetime]: now = datetime.now() From a7bee8642079feeb1585e00c1e212975f74442b0 Mon Sep 17 00:00:00 2001 From: martens Date: Wed, 11 Oct 2023 13:59:02 +0200 Subject: [PATCH 05/22] Fixed some typos and added some changes. --- docs/source/config_examples/iagvt.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/source/config_examples/iagvt.rst b/docs/source/config_examples/iagvt.rst index e66e9e3f..fa33db48 100644 --- a/docs/source/config_examples/iagvt.rst +++ b/docs/source/config_examples/iagvt.rst @@ -53,8 +53,8 @@ Module for performing a fine-acquisition on the sun * A log file where all the offsets from the acquisitions are stored, can be useful for checking the pointing model (line 8). * The (initial) exposure time for the camera (line 11). -* Tolerances for the acquisiton: it succeeds if the telescope is closer than 10" to the position on the sund - get larger than 7200". 10 moves are allowed (lines 14-16). +* Tolerances for the acquisiton: It succeeds if the telescope is closer than 10" to the target position on the sun. + It fails if the offsets get larger than 7200". 10 moves are allowed (lines 14-16). * The pipeline defines steps performed on the images in order to get the offsets for the next step (lines 18-20): #. ``DiskOffset`` is a custom class that takes the WCS from the :ref:`suncamera` and calculates offsets to go @@ -103,8 +103,8 @@ Module for operating the camera looking at the mirror with the two fiber holes FPS: 2 * ``FiberCamera`` is a class derived from :class:`pyobs_aravis.araviscamera.AravisCamera` with some extensions. -* Both fiber holes are defines and one is also defined as the center (lines 5-6). -* The rotation of the camera is given as well as that the image should be flipped (lines 9-10). +* The fiber hole and gregory hole are defined and one is also defined as the image center (lines 5-6). +* The rotation of the camera is given and whether that the image should be flipped (lines 9-10). * The plate scale is given (line 13). * URLs for FITS files and the video stream are defined (lines 16-17). * A SIP calibration is given to be added to the WCS of the FITS file (line 20). @@ -123,7 +123,7 @@ Module used for distributing images among the other modules port: 37075 * :class:`~pyobs.modules.utils.HttpFileCache` provides a HTTP server that can be used for distributing files (line 1). -* It needs a port to run on (line 3). +* It needs a host and port to run on (line 3). filewatcher @@ -176,12 +176,12 @@ Module for operating the Fourier Transform Spectrograph (FTS) * ``FTS`` is a class deriving from :class:`pyobs.modules.camera.basespectrograph.BaseSpectrograph` to implement the functionality of the spectrograph (line 1). -* The rest of the config is omitted here. +* The rest of the config is omitted here, since this class is not freely accessible. gregorycamera """"""""""""" -Module for operating a camera that looks at the gregory hole fiber +Module for operating a camera that looks at the gregory hole of the :ref:`fibercamera`. .. code-block:: YAML :linenos: @@ -287,7 +287,7 @@ Module for running full roboric operations acquisition: acquisition autoguider: guiding -* ``SolarMastermind`` is a custom class that combined the task runner and the scheduler in one class (line 1). +* ``SolarMastermind`` is a custom class that combines the task runner and the scheduler in one class (line 1). * The schedule is calculated using the custom class ``VTTaskSchedule`` (line 3-5). * A task runner is defined which runs a custom script (lines 7-15). From b1bb7ffc626952630f50c31322dc0d1c3371b19a Mon Sep 17 00:00:00 2001 From: GermanHydrogen Date: Mon, 23 Oct 2023 23:20:02 +0200 Subject: [PATCH 06/22] Added local comm --- pyobs/comm/local/__init__.py | 2 + pyobs/comm/local/localcomm.py | 94 ++++++++++++++++++++++++++++++++ pyobs/comm/local/localnetwork.py | 32 +++++++++++ 3 files changed, 128 insertions(+) create mode 100644 pyobs/comm/local/__init__.py create mode 100644 pyobs/comm/local/localcomm.py create mode 100644 pyobs/comm/local/localnetwork.py diff --git a/pyobs/comm/local/__init__.py b/pyobs/comm/local/__init__.py new file mode 100644 index 00000000..0c0f25d3 --- /dev/null +++ b/pyobs/comm/local/__init__.py @@ -0,0 +1,2 @@ +from .localcomm import LocalComm +from .localnetwork import LocalNetwork diff --git a/pyobs/comm/local/localcomm.py b/pyobs/comm/local/localcomm.py new file mode 100644 index 00000000..bebe1af7 --- /dev/null +++ b/pyobs/comm/local/localcomm.py @@ -0,0 +1,94 @@ +import pyobs.comm + +from typing import Optional, List, Type, Dict, Any, Callable, Coroutine + +from pyobs.comm import Comm +from pyobs.events import Event +from pyobs.interfaces import Interface + + +class LocalComm(Comm): + def __init__(self, name: str, *args, **kwargs): + + Comm.__init__(self, *args, **kwargs) + + self._name = name + self._network = pyobs.comm.local.LocalNetwork() + self._network.connect(name, self) + + @property + def name(self) -> Optional[str]: + """Name of this client.""" + return self._name + + @property + def clients(self) -> List[str]: + """Returns list of currently connected clients. + + Returns: + (list) List of currently connected clients. + """ + return self._network.get_client_names() + + async def get_interfaces(self, client: str) -> List[Type[Interface]]: + """Returns list of interfaces for given client. + + Args: + client: Name of client. + + Returns: + List of supported interfaces. + + Raises: + IndexError: If client cannot be found. + """ + if client == self.name: + return self.module.interfaces + + remote_client: LocalComm = self._network.get_client(client) + return await remote_client.get_interfaces(client) + + async def _supports_interface(self, client: str, interface: Type[Interface]) -> bool: + """Checks, whether the given client supports the given interface. + + Args: + client: Client to check. + interface: Interface to check. + + Returns: + Whether or not interface is supported. + """ + interfaces = await self.get_interfaces(client) + return interfaces in interfaces + + async def execute(self, client: str, method: str, annotation: Dict[str, Any], *args: Any) -> Any: + """Execute a given method on a remote client. + + Args: + client (str): ID of client. + method (str): Method to call. + annotation: Method annotation. + *args: List of parameters for given method. + + Returns: + Passes through return from method call. + """ + + remote_client = self._network.get_client(client) + return await remote_client.module.execute(method, *args) + + async def send_event(self, event: Event) -> None: + """Send an event to other clients. + + Args: + event (Event): Event to send + """ + + remote_clients = self._network.get_clients() + for client in remote_clients: + client._send_event_to_module(event, self.name) + + async def _register_events( + self, events: List[Type[Event]], handler: Optional[Callable[[Event, str], Coroutine[Any, Any, bool]]] = None + ) -> None: + pass \ No newline at end of file diff --git a/pyobs/comm/local/localnetwork.py b/pyobs/comm/local/localnetwork.py new file mode 100644 index 00000000..adde274d --- /dev/null +++ b/pyobs/comm/local/localnetwork.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +from typing import Dict, List +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + import pyobs.comm + + +class LocalNetwork: + _instance = None + + def __new__(cls): + if cls._instance is None: + print('Creating the object') + cls._instance = super(LocalNetwork, cls).__new__(cls) + + cls._clients: Dict[str, pyobs.comm.local.LocalComm] = {} + + return cls._instance + + def connect(self, name: str, comm: pyobs.comm.local.LocalComm): + self._clients[name] = comm + + def get_client(self, name: str) -> pyobs.comm.local.LocalComm: + return self._clients[name] + + def get_clients(self) -> List[pyobs.comm.local.LocalComm]: + return list(self._clients.values()) + + def get_client_names(self) -> List[str]: + return list(self._clients.keys()) From 774b3d8e17559988cf18a52e77118922cafc69a9 Mon Sep 17 00:00:00 2001 From: GermanHydrogen Date: Tue, 24 Oct 2023 09:22:28 +0200 Subject: [PATCH 07/22] Added pytest-mock as dev dependency --- poetry.lock | 684 +++++++++++--------------- pyproject.toml | 1 + tests/comm/__init__.py | 0 tests/comm/local/__init__.py | 0 tests/comm/local/test_localcomm.py | 0 tests/comm/local/test_localnetwork.py | 0 6 files changed, 282 insertions(+), 403 deletions(-) create mode 100644 tests/comm/__init__.py create mode 100644 tests/comm/local/__init__.py create mode 100644 tests/comm/local/test_localcomm.py create mode 100644 tests/comm/local/test_localnetwork.py diff --git a/poetry.lock b/poetry.lock index f805147f..fffe8465 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,15 +1,14 @@ -# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiodns" -version = "3.1.0" +version = "3.1.1" description = "Simple DNS resolver for asyncio" -category = "main" optional = false python-versions = "*" files = [ - {file = "aiodns-3.1.0-py3-none-any.whl", hash = "sha256:5268c89765a4a17d6875f70686d6ee406a0d4c40f0b65a0a311fb400aecfedf4"}, - {file = "aiodns-3.1.0.tar.gz", hash = "sha256:fadb59304f4e38fd23b2f1953107df5cf29121d66c4e9c37a9bb13832c0c0513"}, + {file = "aiodns-3.1.1-py3-none-any.whl", hash = "sha256:a387b63da4ced6aad35b1dda2d09620ad608a1c7c0fb71efa07ebb4cd511928d"}, + {file = "aiodns-3.1.1.tar.gz", hash = "sha256:1073eac48185f7a4150cad7f96a5192d6911f12b4fb894de80a088508c9b3a99"}, ] [package.dependencies] @@ -19,7 +18,6 @@ pycares = ">=4.0.0" name = "aiohttp" version = "3.8.6" description = "Async http client/server framework (asyncio)" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -128,7 +126,6 @@ speedups = ["Brotli", "aiodns", "cchardet"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -143,7 +140,6 @@ frozenlist = ">=1.1.0" name = "anyio" version = "4.0.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" -category = "main" optional = true python-versions = ">=3.8" files = [ @@ -165,7 +161,6 @@ trio = ["trio (>=0.22)"] name = "asciitree" version = "0.3.3" description = "Draws ASCII trees." -category = "main" optional = true python-versions = "*" files = [ @@ -176,7 +171,6 @@ files = [ name = "asteval" version = "0.9.31" description = "Safe, minimalistic evaluator of python expression using ast module" -category = "main" optional = true python-versions = ">=3.7" files = [ @@ -194,7 +188,6 @@ test = ["coverage", "pytest", "pytest-cov"] name = "astroplan" version = "0.9.1" description = "Observation planning package for astronomers" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -217,7 +210,6 @@ test = ["pytest-astropy", "pytest-mpl"] name = "astropy" version = "5.3.4" description = "Astronomy and astrophysics core library" -category = "main" optional = false python-versions = ">=3.9" files = [ @@ -269,7 +261,6 @@ test-all = ["coverage[toml]", "ipython (>=4.2)", "objgraph", "pytest (>=7.0,<8)" name = "astropy-healpix" version = "1.0.0" description = "BSD-licensed HEALPix for Astropy" -category = "main" optional = true python-versions = ">=3.9" files = [ @@ -295,7 +286,6 @@ test = ["hypothesis", "pytest-astropy"] name = "astroquery" version = "0.4.6" description = "Functions and classes to access online astronomical data resources" -category = "main" optional = false python-versions = "*" files = [ @@ -321,7 +311,6 @@ test = ["flask", "jinja2", "matplotlib", "pytest-astropy", "pytest-dependency"] name = "astroscrappy" version = "1.1.0" description = "Speedy Cosmic Ray Annihilation Package in Python" -category = "main" optional = true python-versions = ">=3.6" files = [ @@ -353,7 +342,6 @@ test = ["Cython", "pytest-astropy", "scipy"] name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -365,7 +353,6 @@ files = [ name = "asyncinotify" version = "4.0.2" description = "A simple optionally-async python inotify library, focused on simplicity of use and operation, and leveraging modern Python features" -category = "main" optional = false python-versions = ">= 3.6, < 4" files = [ @@ -377,7 +364,6 @@ files = [ name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -396,7 +382,6 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "bcrypt" version = "4.0.1" description = "Modern password hashing for your software and your servers" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -431,7 +416,6 @@ typecheck = ["mypy"] name = "beautifulsoup4" version = "4.12.2" description = "Screen-scraping library" -category = "main" optional = false python-versions = ">=3.6.0" files = [ @@ -448,34 +432,29 @@ lxml = ["lxml"] [[package]] name = "black" -version = "23.9.1" +version = "23.10.1" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"}, - {file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"}, - {file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"}, - {file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"}, - {file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"}, - {file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"}, - {file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"}, - {file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"}, - {file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"}, - {file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"}, - {file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"}, - {file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"}, - {file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"}, - {file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"}, - {file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"}, - {file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"}, - {file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"}, - {file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"}, - {file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"}, - {file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"}, - {file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"}, - {file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"}, + {file = "black-23.10.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:ec3f8e6234c4e46ff9e16d9ae96f4ef69fa328bb4ad08198c8cee45bb1f08c69"}, + {file = "black-23.10.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:1b917a2aa020ca600483a7b340c165970b26e9029067f019e3755b56e8dd5916"}, + {file = "black-23.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c74de4c77b849e6359c6f01987e94873c707098322b91490d24296f66d067dc"}, + {file = "black-23.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:7b4d10b0f016616a0d93d24a448100adf1699712fb7a4efd0e2c32bbb219b173"}, + {file = "black-23.10.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:b15b75fc53a2fbcac8a87d3e20f69874d161beef13954747e053bca7a1ce53a0"}, + {file = "black-23.10.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:e293e4c2f4a992b980032bbd62df07c1bcff82d6964d6c9496f2cd726e246ace"}, + {file = "black-23.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d56124b7a61d092cb52cce34182a5280e160e6aff3137172a68c2c2c4b76bcb"}, + {file = "black-23.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:3f157a8945a7b2d424da3335f7ace89c14a3b0625e6593d21139c2d8214d55ce"}, + {file = "black-23.10.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:cfcce6f0a384d0da692119f2d72d79ed07c7159879d0bb1bb32d2e443382bf3a"}, + {file = "black-23.10.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:33d40f5b06be80c1bbce17b173cda17994fbad096ce60eb22054da021bf933d1"}, + {file = "black-23.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:840015166dbdfbc47992871325799fd2dc0dcf9395e401ada6d88fe11498abad"}, + {file = "black-23.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:037e9b4664cafda5f025a1728c50a9e9aedb99a759c89f760bd83730e76ba884"}, + {file = "black-23.10.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:7cb5936e686e782fddb1c73f8aa6f459e1ad38a6a7b0e54b403f1f05a1507ee9"}, + {file = "black-23.10.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:7670242e90dc129c539e9ca17665e39a146a761e681805c54fbd86015c7c84f7"}, + {file = "black-23.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ed45ac9a613fb52dad3b61c8dea2ec9510bf3108d4db88422bacc7d1ba1243d"}, + {file = "black-23.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:6d23d7822140e3fef190734216cefb262521789367fbdc0b3f22af6744058982"}, + {file = "black-23.10.1-py3-none-any.whl", hash = "sha256:d431e6739f727bb2e0495df64a6c7a5310758e87505f5f8cde9ff6c0f2d7e4fe"}, + {file = "black-23.10.1.tar.gz", hash = "sha256:1f8ce316753428ff68749c65a5f7844631aa18c8679dfd3ca9dc1a289979c258"}, ] [package.dependencies] @@ -497,7 +476,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "ccdproc" version = "2.4.1" description = "Astropy affiliated package" -category = "main" optional = true python-versions = ">=3.8" files = [ @@ -521,7 +499,6 @@ test = ["memory-profiler", "pytest-astropy (>=0.10.0)"] name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -533,7 +510,6 @@ files = [ name = "cffi" version = "1.16.0" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -598,7 +574,6 @@ pycparser = "*" name = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -608,109 +583,107 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.3.0" +version = "3.3.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.0.tar.gz", hash = "sha256:63563193aec44bce707e0c5ca64ff69fa72ed7cf34ce6e11d5127555756fd2f6"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:effe5406c9bd748a871dbcaf3ac69167c38d72db8c9baf3ff954c344f31c4cbe"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4162918ef3098851fcd8a628bf9b6a98d10c380725df9e04caf5ca6dd48c847a"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0570d21da019941634a531444364f2482e8db0b3425fcd5ac0c36565a64142c8"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5707a746c6083a3a74b46b3a631d78d129edab06195a92a8ece755aac25a3f3d"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:278c296c6f96fa686d74eb449ea1697f3c03dc28b75f873b65b5201806346a69"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4b71f4d1765639372a3b32d2638197f5cd5221b19531f9245fcc9ee62d38f56"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f93dab657839dfa61025056606600a11d0b696d79386f974e459a3fbc568ec"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:db756e48f9c5c607b5e33dd36b1d5872d0422e960145b08ab0ec7fd420e9d649"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:232ac332403e37e4a03d209a3f92ed9071f7d3dbda70e2a5e9cff1c4ba9f0678"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:e5c1502d4ace69a179305abb3f0bb6141cbe4714bc9b31d427329a95acfc8bdd"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2502dd2a736c879c0f0d3e2161e74d9907231e25d35794584b1ca5284e43f596"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23e8565ab7ff33218530bc817922fae827420f143479b753104ab801145b1d5b"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-win32.whl", hash = "sha256:1872d01ac8c618a8da634e232f24793883d6e456a66593135aeafe3784b0848d"}, - {file = "charset_normalizer-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:557b21a44ceac6c6b9773bc65aa1b4cc3e248a5ad2f5b914b91579a32e22204d"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7eff0f27edc5afa9e405f7165f85a6d782d308f3b6b9d96016c010597958e63"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6a685067d05e46641d5d1623d7c7fdf15a357546cbb2f71b0ebde91b175ffc3e"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0d3d5b7db9ed8a2b11a774db2bbea7ba1884430a205dbd54a32d61d7c2a190fa"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2935ffc78db9645cb2086c2f8f4cfd23d9b73cc0dc80334bc30aac6f03f68f8c"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fe359b2e3a7729010060fbca442ca225280c16e923b37db0e955ac2a2b72a05"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:380c4bde80bce25c6e4f77b19386f5ec9db230df9f2f2ac1e5ad7af2caa70459"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0d1e3732768fecb052d90d62b220af62ead5748ac51ef61e7b32c266cac9293"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1b2919306936ac6efb3aed1fbf81039f7087ddadb3160882a57ee2ff74fd2382"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:82eb849f085624f6a607538ee7b83a6d8126df6d2f7d3b319cb837b289123078"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7b8b8bf1189b3ba9b8de5c8db4d541b406611a71a955bbbd7385bbc45fcb786c"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5adf257bd58c1b8632046bbe43ee38c04e1038e9d37de9c57a94d6bd6ce5da34"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c350354efb159b8767a6244c166f66e67506e06c8924ed74669b2c70bc8735b1"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-win32.whl", hash = "sha256:02af06682e3590ab952599fbadac535ede5d60d78848e555aa58d0c0abbde786"}, - {file = "charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:86d1f65ac145e2c9ed71d8ffb1905e9bba3a91ae29ba55b4c46ae6fc31d7c0d4"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3b447982ad46348c02cb90d230b75ac34e9886273df3a93eec0539308a6296d7"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:abf0d9f45ea5fb95051c8bfe43cb40cda383772f7e5023a83cc481ca2604d74e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b09719a17a2301178fac4470d54b1680b18a5048b481cb8890e1ef820cb80455"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3d9b48ee6e3967b7901c052b670c7dda6deb812c309439adaffdec55c6d7b78"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:edfe077ab09442d4ef3c52cb1f9dab89bff02f4524afc0acf2d46be17dc479f5"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3debd1150027933210c2fc321527c2299118aa929c2f5a0a80ab6953e3bd1908"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f63face3a527284f7bb8a9d4f78988e3c06823f7bea2bd6f0e0e9298ca0403"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24817cb02cbef7cd499f7c9a2735286b4782bd47a5b3516a0e84c50eab44b98e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c71f16da1ed8949774ef79f4a0260d28b83b3a50c6576f8f4f0288d109777989"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9cf3126b85822c4e53aa28c7ec9869b924d6fcfb76e77a45c44b83d91afd74f9"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:b3b2316b25644b23b54a6f6401074cebcecd1244c0b8e80111c9a3f1c8e83d65"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:03680bb39035fbcffe828eae9c3f8afc0428c91d38e7d61aa992ef7a59fb120e"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4cc152c5dd831641e995764f9f0b6589519f6f5123258ccaca8c6d34572fefa8"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-win32.whl", hash = "sha256:b8f3307af845803fb0b060ab76cf6dd3a13adc15b6b451f54281d25911eb92df"}, - {file = "charset_normalizer-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:8eaf82f0eccd1505cf39a45a6bd0a8cf1c70dcfc30dba338207a969d91b965c0"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dc45229747b67ffc441b3de2f3ae5e62877a282ea828a5bdb67883c4ee4a8810"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f4a0033ce9a76e391542c182f0d48d084855b5fcba5010f707c8e8c34663d77"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada214c6fa40f8d800e575de6b91a40d0548139e5dc457d2ebb61470abf50186"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1121de0e9d6e6ca08289583d7491e7fcb18a439305b34a30b20d8215922d43c"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1063da2c85b95f2d1a430f1c33b55c9c17ffaf5e612e10aeaad641c55a9e2b9d"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70f1d09c0d7748b73290b29219e854b3207aea922f839437870d8cc2168e31cc"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:250c9eb0f4600361dd80d46112213dff2286231d92d3e52af1e5a6083d10cad9"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:750b446b2ffce1739e8578576092179160f6d26bd5e23eb1789c4d64d5af7dc7"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:588245972aca710b5b68802c8cad9edaa98589b1b42ad2b53accd6910dad3545"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e39c7eb31e3f5b1f88caff88bcff1b7f8334975b46f6ac6e9fc725d829bc35d4"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-win32.whl", hash = "sha256:abecce40dfebbfa6abf8e324e1860092eeca6f7375c8c4e655a8afb61af58f2c"}, - {file = "charset_normalizer-3.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a91a981f185721542a0b7c92e9054b7ab4fea0508a795846bc5b0abf8118d4"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:67b8cc9574bb518ec76dc8e705d4c39ae78bb96237cb533edac149352c1f39fe"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac71b2977fb90c35d41c9453116e283fac47bb9096ad917b8819ca8b943abecd"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3ae38d325b512f63f8da31f826e6cb6c367336f95e418137286ba362925c877e"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:542da1178c1c6af8873e143910e2269add130a299c9106eef2594e15dae5e482"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:30a85aed0b864ac88309b7d94be09f6046c834ef60762a8833b660139cfbad13"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aae32c93e0f64469f74ccc730a7cb21c7610af3a775157e50bbd38f816536b38"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b26ddf78d57f1d143bdf32e820fd8935d36abe8a25eb9ec0b5a71c82eb3895"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f5d10bae5d78e4551b7be7a9b29643a95aded9d0f602aa2ba584f0388e7a557"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:249c6470a2b60935bafd1d1d13cd613f8cd8388d53461c67397ee6a0f5dce741"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c5a74c359b2d47d26cdbbc7845e9662d6b08a1e915eb015d044729e92e7050b7"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b5bcf60a228acae568e9911f410f9d9e0d43197d030ae5799e20dca8df588287"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:187d18082694a29005ba2944c882344b6748d5be69e3a89bf3cc9d878e548d5a"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:81bf654678e575403736b85ba3a7867e31c2c30a69bc57fe88e3ace52fb17b89"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-win32.whl", hash = "sha256:85a32721ddde63c9df9ebb0d2045b9691d9750cb139c161c80e500d210f5e26e"}, - {file = "charset_normalizer-3.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:468d2a840567b13a590e67dd276c570f8de00ed767ecc611994c301d0f8c014f"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e0fc42822278451bc13a2e8626cf2218ba570f27856b536e00cfa53099724828"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:09c77f964f351a7369cc343911e0df63e762e42bac24cd7d18525961c81754f4"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:12ebea541c44fdc88ccb794a13fe861cc5e35d64ed689513a5c03d05b53b7c82"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:805dfea4ca10411a5296bcc75638017215a93ffb584c9e344731eef0dcfb026a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:96c2b49eb6a72c0e4991d62406e365d87067ca14c1a729a870d22354e6f68115"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aaf7b34c5bc56b38c931a54f7952f1ff0ae77a2e82496583b247f7c969eb1479"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:619d1c96099be5823db34fe89e2582b336b5b074a7f47f819d6b3a57ff7bdb86"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a0ac5e7015a5920cfce654c06618ec40c33e12801711da6b4258af59a8eff00a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93aa7eef6ee71c629b51ef873991d6911b906d7312c6e8e99790c0f33c576f89"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7966951325782121e67c81299a031f4c115615e68046f79b85856b86ebffc4cd"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:02673e456dc5ab13659f85196c534dc596d4ef260e4d86e856c3b2773ce09843"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:c2af80fb58f0f24b3f3adcb9148e6203fa67dd3f61c4af146ecad033024dde43"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:153e7b6e724761741e0974fc4dcd406d35ba70b92bfe3fedcb497226c93b9da7"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-win32.whl", hash = "sha256:d47ecf253780c90ee181d4d871cd655a789da937454045b17b5798da9393901a"}, - {file = "charset_normalizer-3.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:d97d85fa63f315a8bdaba2af9a6a686e0eceab77b3089af45133252618e70884"}, - {file = "charset_normalizer-3.3.0-py3-none-any.whl", hash = "sha256:e46cd37076971c1040fc8c41273a8b3e2c624ce4f2be3f5dfcb7a430c1d3acc2"}, + {file = "charset-normalizer-3.3.1.tar.gz", hash = "sha256:d9137a876020661972ca6eec0766d81aef8a5627df628b664b234b73396e727e"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8aee051c89e13565c6bd366813c386939f8e928af93c29fda4af86d25b73d8f8"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:352a88c3df0d1fa886562384b86f9a9e27563d4704ee0e9d56ec6fcd270ea690"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:223b4d54561c01048f657fa6ce41461d5ad8ff128b9678cfe8b2ecd951e3f8a2"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f861d94c2a450b974b86093c6c027888627b8082f1299dfd5a4bae8e2292821"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1171ef1fc5ab4693c5d151ae0fdad7f7349920eabbaca6271f95969fa0756c2d"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28f512b9a33235545fbbdac6a330a510b63be278a50071a336afc1b78781b147"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0e842112fe3f1a4ffcf64b06dc4c61a88441c2f02f373367f7b4c1aa9be2ad5"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f9bc2ce123637a60ebe819f9fccc614da1bcc05798bbbaf2dd4ec91f3e08846"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f194cce575e59ffe442c10a360182a986535fd90b57f7debfaa5c845c409ecc3"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9a74041ba0bfa9bc9b9bb2cd3238a6ab3b7618e759b41bd15b5f6ad958d17605"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b578cbe580e3b41ad17b1c428f382c814b32a6ce90f2d8e39e2e635d49e498d1"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6db3cfb9b4fcecb4390db154e75b49578c87a3b9979b40cdf90d7e4b945656e1"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:debb633f3f7856f95ad957d9b9c781f8e2c6303ef21724ec94bea2ce2fcbd056"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-win32.whl", hash = "sha256:87071618d3d8ec8b186d53cb6e66955ef2a0e4fa63ccd3709c0c90ac5a43520f"}, + {file = "charset_normalizer-3.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:e372d7dfd154009142631de2d316adad3cc1c36c32a38b16a4751ba78da2a397"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae4070f741f8d809075ef697877fd350ecf0b7c5837ed68738607ee0a2c572cf"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:58e875eb7016fd014c0eea46c6fa92b87b62c0cb31b9feae25cbbe62c919f54d"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbd95e300367aa0827496fe75a1766d198d34385a58f97683fe6e07f89ca3e3c"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de0b4caa1c8a21394e8ce971997614a17648f94e1cd0640fbd6b4d14cab13a72"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:985c7965f62f6f32bf432e2681173db41336a9c2611693247069288bcb0c7f8b"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a15c1fe6d26e83fd2e5972425a772cca158eae58b05d4a25a4e474c221053e2d"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae55d592b02c4349525b6ed8f74c692509e5adffa842e582c0f861751701a673"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be4d9c2770044a59715eb57c1144dedea7c5d5ae80c68fb9959515037cde2008"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:851cf693fb3aaef71031237cd68699dded198657ec1e76a76eb8be58c03a5d1f"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:31bbaba7218904d2eabecf4feec0d07469284e952a27400f23b6628439439fa7"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:871d045d6ccc181fd863a3cd66ee8e395523ebfbc57f85f91f035f50cee8e3d4"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:501adc5eb6cd5f40a6f77fbd90e5ab915c8fd6e8c614af2db5561e16c600d6f3"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f5fb672c396d826ca16a022ac04c9dce74e00a1c344f6ad1a0fdc1ba1f332213"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-win32.whl", hash = "sha256:bb06098d019766ca16fc915ecaa455c1f1cd594204e7f840cd6258237b5079a8"}, + {file = "charset_normalizer-3.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:8af5a8917b8af42295e86b64903156b4f110a30dca5f3b5aedea123fbd638bff"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7ae8e5142dcc7a49168f4055255dbcced01dc1714a90a21f87448dc8d90617d1"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5b70bab78accbc672f50e878a5b73ca692f45f5b5e25c8066d748c09405e6a55"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5ceca5876032362ae73b83347be8b5dbd2d1faf3358deb38c9c88776779b2e2f"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34d95638ff3613849f473afc33f65c401a89f3b9528d0d213c7037c398a51296"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9edbe6a5bf8b56a4a84533ba2b2f489d0046e755c29616ef8830f9e7d9cf5728"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6a02a3c7950cafaadcd46a226ad9e12fc9744652cc69f9e5534f98b47f3bbcf"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10b8dd31e10f32410751b3430996f9807fc4d1587ca69772e2aa940a82ab571a"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edc0202099ea1d82844316604e17d2b175044f9bcb6b398aab781eba957224bd"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b891a2f68e09c5ef989007fac11476ed33c5c9994449a4e2c3386529d703dc8b"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:71ef3b9be10070360f289aea4838c784f8b851be3ba58cf796262b57775c2f14"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:55602981b2dbf8184c098bc10287e8c245e351cd4fdcad050bd7199d5a8bf514"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:46fb9970aa5eeca547d7aa0de5d4b124a288b42eaefac677bde805013c95725c"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:520b7a142d2524f999447b3a0cf95115df81c4f33003c51a6ab637cbda9d0bf4"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-win32.whl", hash = "sha256:8ec8ef42c6cd5856a7613dcd1eaf21e5573b2185263d87d27c8edcae33b62a61"}, + {file = "charset_normalizer-3.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:baec8148d6b8bd5cee1ae138ba658c71f5b03e0d69d5907703e3e1df96db5e41"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63a6f59e2d01310f754c270e4a257426fe5a591dc487f1983b3bbe793cf6bac6"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d6bfc32a68bc0933819cfdfe45f9abc3cae3877e1d90aac7259d57e6e0f85b1"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f3100d86dcd03c03f7e9c3fdb23d92e32abbca07e7c13ebd7ddfbcb06f5991f"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39b70a6f88eebe239fa775190796d55a33cfb6d36b9ffdd37843f7c4c1b5dc67"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e12f8ee80aa35e746230a2af83e81bd6b52daa92a8afaef4fea4a2ce9b9f4fa"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b6cefa579e1237ce198619b76eaa148b71894fb0d6bcf9024460f9bf30fd228"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:61f1e3fb621f5420523abb71f5771a204b33c21d31e7d9d86881b2cffe92c47c"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4f6e2a839f83a6a76854d12dbebde50e4b1afa63e27761549d006fa53e9aa80e"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:1ec937546cad86d0dce5396748bf392bb7b62a9eeb8c66efac60e947697f0e58"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:82ca51ff0fc5b641a2d4e1cc8c5ff108699b7a56d7f3ad6f6da9dbb6f0145b48"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:633968254f8d421e70f91c6ebe71ed0ab140220469cf87a9857e21c16687c034"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-win32.whl", hash = "sha256:c0c72d34e7de5604df0fde3644cc079feee5e55464967d10b24b1de268deceb9"}, + {file = "charset_normalizer-3.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:63accd11149c0f9a99e3bc095bbdb5a464862d77a7e309ad5938fbc8721235ae"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5a3580a4fdc4ac05f9e53c57f965e3594b2f99796231380adb2baaab96e22761"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2465aa50c9299d615d757c1c888bc6fef384b7c4aec81c05a0172b4400f98557"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb7cd68814308aade9d0c93c5bd2ade9f9441666f8ba5aa9c2d4b389cb5e2a45"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91e43805ccafa0a91831f9cd5443aa34528c0c3f2cc48c4cb3d9a7721053874b"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:854cc74367180beb327ab9d00f964f6d91da06450b0855cbbb09187bcdb02de5"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c15070ebf11b8b7fd1bfff7217e9324963c82dbdf6182ff7050519e350e7ad9f"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4c99f98fc3a1835af8179dcc9013f93594d0670e2fa80c83aa36346ee763d2"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fb765362688821404ad6cf86772fc54993ec11577cd5a92ac44b4c2ba52155b"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dced27917823df984fe0c80a5c4ad75cf58df0fbfae890bc08004cd3888922a2"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a66bcdf19c1a523e41b8e9d53d0cedbfbac2e93c649a2e9502cb26c014d0980c"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:ecd26be9f112c4f96718290c10f4caea6cc798459a3a76636b817a0ed7874e42"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:3f70fd716855cd3b855316b226a1ac8bdb3caf4f7ea96edcccc6f484217c9597"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:17a866d61259c7de1bdadef418a37755050ddb4b922df8b356503234fff7932c"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-win32.whl", hash = "sha256:548eefad783ed787b38cb6f9a574bd8664468cc76d1538215d510a3cd41406cb"}, + {file = "charset_normalizer-3.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:45f053a0ece92c734d874861ffe6e3cc92150e32136dd59ab1fb070575189c97"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bc791ec3fd0c4309a753f95bb6c749ef0d8ea3aea91f07ee1cf06b7b02118f2f"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0c8c61fb505c7dad1d251c284e712d4e0372cef3b067f7ddf82a7fa82e1e9a93"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2c092be3885a1b7899cd85ce24acedc1034199d6fca1483fa2c3a35c86e43041"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2000c54c395d9e5e44c99dc7c20a64dc371f777faf8bae4919ad3e99ce5253e"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cb50a0335382aac15c31b61d8531bc9bb657cfd848b1d7158009472189f3d62"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c30187840d36d0ba2893bc3271a36a517a717f9fd383a98e2697ee890a37c273"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe81b35c33772e56f4b6cf62cf4aedc1762ef7162a31e6ac7fe5e40d0149eb67"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0bf89afcbcf4d1bb2652f6580e5e55a840fdf87384f6063c4a4f0c95e378656"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:06cf46bdff72f58645434d467bf5228080801298fbba19fe268a01b4534467f5"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3c66df3f41abee950d6638adc7eac4730a306b022570f71dd0bd6ba53503ab57"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd805513198304026bd379d1d516afbf6c3c13f4382134a2c526b8b854da1c2e"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:9505dc359edb6a330efcd2be825fdb73ee3e628d9010597aa1aee5aa63442e97"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:31445f38053476a0c4e6d12b047b08ced81e2c7c712e5a1ad97bc913256f91b2"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-win32.whl", hash = "sha256:bd28b31730f0e982ace8663d108e01199098432a30a4c410d06fe08fdb9e93f4"}, + {file = "charset_normalizer-3.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:555fe186da0068d3354cdf4bbcbc609b0ecae4d04c921cc13e209eece7720727"}, + {file = "charset_normalizer-3.3.1-py3-none-any.whl", hash = "sha256:800561453acdecedaac137bf09cd719c7a440b6800ec182f077bb8e7025fb708"}, ] [[package]] name = "click" version = "8.1.7" description = "Composable command line interface toolkit" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -723,21 +696,19 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "cloudpickle" -version = "2.2.1" -description = "Extended pickling support for Python objects" -category = "main" +version = "3.0.0" +description = "Pickler class to extend the standard pickle.Pickler functionality" optional = true -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "cloudpickle-2.2.1-py3-none-any.whl", hash = "sha256:61f594d1f4c295fa5cd9014ceb3a1fc4a70b0de1164b94fbc2d854ccba056f9f"}, - {file = "cloudpickle-2.2.1.tar.gz", hash = "sha256:d89684b8de9e34a2a43b3460fbca07d09d6e25ce858df4d5a44240403b6178f5"}, + {file = "cloudpickle-3.0.0-py3-none-any.whl", hash = "sha256:246ee7d0c295602a036e86369c77fecda4ab17b506496730f2f576d9016fd9c7"}, + {file = "cloudpickle-3.0.0.tar.gz", hash = "sha256:996d9a482c6fb4f33c1a35335cf8afd065d2a56e973270364840712d9131a882"}, ] [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -749,7 +720,6 @@ files = [ name = "cryptography" version = "41.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -793,14 +763,13 @@ test-randomorder = ["pytest-randomly"] [[package]] name = "dask" -version = "2023.9.3" +version = "2023.10.0" description = "Parallel PyData with Task Scheduling" -category = "main" optional = true python-versions = ">=3.9" files = [ - {file = "dask-2023.9.3-py3-none-any.whl", hash = "sha256:2e6c5de0dfce114ca9c0bb2b79d72d3a9e1499bf25cfef477659179614df5692"}, - {file = "dask-2023.9.3.tar.gz", hash = "sha256:712090788a7822d538cf8f43f5afc887eebe7471bc46cc331424e1cfa248764c"}, + {file = "dask-2023.10.0-py3-none-any.whl", hash = "sha256:cb133919ff6f1fc021efe1eef24df0e4acecf33a7708e7b04d2dea6b45e166bb"}, + {file = "dask-2023.10.0.tar.gz", hash = "sha256:3fdfdbdb5f9f3a556487bf37142e5a730dab2f2c8eca0b6c79d11199c30220e3"}, ] [package.dependencies] @@ -819,14 +788,13 @@ array = ["numpy (>=1.21)"] complete = ["dask[array,dataframe,diagnostics,distributed]", "lz4 (>=4.3.2)", "pyarrow (>=7.0)"] dataframe = ["dask[array]", "pandas (>=1.3)"] diagnostics = ["bokeh (>=2.4.2)", "jinja2 (>=2.10.3)"] -distributed = ["distributed (==2023.9.3)"] +distributed = ["distributed (==2023.10.0)"] test = ["pandas[test]", "pre-commit", "pytest", "pytest-cov", "pytest-rerunfailures", "pytest-timeout", "pytest-xdist"] [[package]] name = "dbus-next" version = "0.2.3" description = "A zero-dependency DBus library for Python with asyncio support" -category = "main" optional = false python-versions = ">=3.6.0" files = [ @@ -838,7 +806,6 @@ files = [ name = "distlib" version = "0.3.7" description = "Distribution utilities" -category = "dev" optional = false python-versions = "*" files = [ @@ -850,7 +817,6 @@ files = [ name = "docutils" version = "0.20.1" description = "Docutils -- Python Documentation Utilities" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -862,7 +828,6 @@ files = [ name = "exceptiongroup" version = "1.1.3" description = "Backport of PEP 654 (exception groups)" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -877,7 +842,6 @@ test = ["pytest (>=6)"] name = "fasteners" version = "0.19" description = "A python package that provides useful locks" -category = "main" optional = true python-versions = ">=3.6" files = [ @@ -889,7 +853,6 @@ files = [ name = "filelock" version = "3.12.4" description = "A platform independent file lock." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -906,7 +869,6 @@ typing = ["typing-extensions (>=4.7.1)"] name = "frozenlist" version = "1.4.0" description = "A list-like structure which implements collections.abc.MutableSequence" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -975,14 +937,13 @@ files = [ [[package]] name = "fsspec" -version = "2023.9.2" +version = "2023.10.0" description = "File-system specification" -category = "main" optional = true python-versions = ">=3.8" files = [ - {file = "fsspec-2023.9.2-py3-none-any.whl", hash = "sha256:603dbc52c75b84da501b9b2ec8c11e1f61c25984c4a0dda1f129ef391fbfc9b4"}, - {file = "fsspec-2023.9.2.tar.gz", hash = "sha256:80bfb8c70cc27b2178cc62a935ecf242fc6e8c3fb801f9c571fc01b1e715ba7d"}, + {file = "fsspec-2023.10.0-py3-none-any.whl", hash = "sha256:346a8f024efeb749d2a5fca7ba8854474b1ff9af7c3faaf636a4548781136529"}, + {file = "fsspec-2023.10.0.tar.gz", hash = "sha256:330c66757591df346ad3091a53bd907e15348c2ba17d63fd54f5c39c4457d2a5"}, ] [package.extras] @@ -1013,7 +974,6 @@ tqdm = ["tqdm"] name = "future" version = "0.18.3" description = "Clean single-source support for Python 3 and 2" -category = "main" optional = true python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1024,7 +984,6 @@ files = [ name = "h11" version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -category = "main" optional = true python-versions = ">=3.7" files = [ @@ -1036,7 +995,6 @@ files = [ name = "html5lib" version = "1.1" description = "HTML parser based on the WHATWG HTML specification" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -1058,7 +1016,6 @@ lxml = ["lxml"] name = "httpcore" version = "0.18.0" description = "A minimal low-level HTTP client." -category = "main" optional = true python-versions = ">=3.8" files = [ @@ -1070,17 +1027,16 @@ files = [ anyio = ">=3.0,<5.0" certifi = "*" h11 = ">=0.13,<0.15" -sniffio = ">=1.0.0,<2.0.0" +sniffio = "==1.*" [package.extras] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] [[package]] name = "httpx" version = "0.25.0" description = "The next generation HTTP client." -category = "main" optional = true python-versions = ">=3.8" files = [ @@ -1096,15 +1052,14 @@ sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] -cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<14)"] +cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] -socks = ["socksio (>=1.0.0,<2.0.0)"] +socks = ["socksio (==1.*)"] [[package]] name = "identify" version = "2.5.30" description = "File identification library for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1119,7 +1074,6 @@ license = ["ukkonen"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1129,19 +1083,18 @@ files = [ [[package]] name = "imageio" -version = "2.31.5" +version = "2.31.6" description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats." -category = "main" optional = true python-versions = ">=3.8" files = [ - {file = "imageio-2.31.5-py3-none-any.whl", hash = "sha256:97f68e12ba676f2f4b541684ed81f7f3370dc347e8321bc68ee34d37b2dbac9f"}, - {file = "imageio-2.31.5.tar.gz", hash = "sha256:d8e53f9cd4054880276a3dac0a28c85ba7874084856a55a0294a8ae6ed7f3a8e"}, + {file = "imageio-2.31.6-py3-none-any.whl", hash = "sha256:70410af62626a4d725b726ab59138e211e222b80ddf8201c7a6561d694c6238e"}, + {file = "imageio-2.31.6.tar.gz", hash = "sha256:721f238896a9a99a77b73f06f42fc235d477d5d378cdf34dd0bee1e408b4742c"}, ] [package.dependencies] numpy = "*" -pillow = ">=8.3.2" +pillow = ">=8.3.2,<10.1.0" [package.extras] all-plugins = ["astropy", "av", "imageio-ffmpeg", "psutil", "tifffile"] @@ -1163,7 +1116,6 @@ tifffile = ["tifffile"] name = "importlib-metadata" version = "6.8.0" description = "Read metadata from Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1183,7 +1135,6 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1195,7 +1146,6 @@ files = [ name = "jaraco-classes" version = "3.3.0" description = "Utility functions for Python class constructs" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1214,7 +1164,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "jeepney" version = "0.8.0" description = "Low-level, pure Python DBus protocol wrapper." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1230,7 +1179,6 @@ trio = ["async_generator", "trio"] name = "keyring" version = "24.2.0" description = "Store and access your passwords safely." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1254,7 +1202,6 @@ testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", name = "lazy-loader" version = "0.3" description = "lazy_loader" -category = "main" optional = true python-versions = ">=3.7" files = [ @@ -1270,7 +1217,6 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"] name = "lmfit" version = "1.2.2" description = "Least-Squares Minimization with Bounds and Constraints" -category = "main" optional = true python-versions = ">=3.7" files = [ @@ -1294,7 +1240,6 @@ test = ["coverage", "flaky", "pytest", "pytest-cov"] name = "locket" version = "1.0.0" description = "File-based locks for Python on Linux and Windows" -category = "main" optional = true python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1306,7 +1251,6 @@ files = [ name = "lockfile" version = "0.12.2" description = "Platform-independent file locking module" -category = "main" optional = false python-versions = "*" files = [ @@ -1318,7 +1262,6 @@ files = [ name = "more-itertools" version = "10.1.0" description = "More routines for operating on iterables, beyond itertools" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1330,7 +1273,6 @@ files = [ name = "multidict" version = "6.0.4" description = "multidict implementation" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1412,39 +1354,38 @@ files = [ [[package]] name = "mypy" -version = "1.5.1" +version = "1.6.1" description = "Optional static typing for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "mypy-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70"}, - {file = "mypy-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0"}, - {file = "mypy-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12"}, - {file = "mypy-1.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d"}, - {file = "mypy-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25"}, - {file = "mypy-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4"}, - {file = "mypy-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4"}, - {file = "mypy-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243"}, - {file = "mypy-1.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275"}, - {file = "mypy-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315"}, - {file = "mypy-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb"}, - {file = "mypy-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373"}, - {file = "mypy-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161"}, - {file = "mypy-1.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a"}, - {file = "mypy-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1"}, - {file = "mypy-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65"}, - {file = "mypy-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160"}, - {file = "mypy-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2"}, - {file = "mypy-1.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb"}, - {file = "mypy-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"}, - {file = "mypy-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a"}, - {file = "mypy-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14"}, - {file = "mypy-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb"}, - {file = "mypy-1.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693"}, - {file = "mypy-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770"}, - {file = "mypy-1.5.1-py3-none-any.whl", hash = "sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5"}, - {file = "mypy-1.5.1.tar.gz", hash = "sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92"}, + {file = "mypy-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e5012e5cc2ac628177eaac0e83d622b2dd499e28253d4107a08ecc59ede3fc2c"}, + {file = "mypy-1.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d8fbb68711905f8912e5af474ca8b78d077447d8f3918997fecbf26943ff3cbb"}, + {file = "mypy-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a1ad938fee7d2d96ca666c77b7c494c3c5bd88dff792220e1afbebb2925b5e"}, + {file = "mypy-1.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b96ae2c1279d1065413965c607712006205a9ac541895004a1e0d4f281f2ff9f"}, + {file = "mypy-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:40b1844d2e8b232ed92e50a4bd11c48d2daa351f9deee6c194b83bf03e418b0c"}, + {file = "mypy-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81af8adaa5e3099469e7623436881eff6b3b06db5ef75e6f5b6d4871263547e5"}, + {file = "mypy-1.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8c223fa57cb154c7eab5156856c231c3f5eace1e0bed9b32a24696b7ba3c3245"}, + {file = "mypy-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8032e00ce71c3ceb93eeba63963b864bf635a18f6c0c12da6c13c450eedb183"}, + {file = "mypy-1.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4c46b51de523817a0045b150ed11b56f9fff55f12b9edd0f3ed35b15a2809de0"}, + {file = "mypy-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:19f905bcfd9e167159b3d63ecd8cb5e696151c3e59a1742e79bc3bcb540c42c7"}, + {file = "mypy-1.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:82e469518d3e9a321912955cc702d418773a2fd1e91c651280a1bda10622f02f"}, + {file = "mypy-1.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d4473c22cc296425bbbce7e9429588e76e05bc7342da359d6520b6427bf76660"}, + {file = "mypy-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59a0d7d24dfb26729e0a068639a6ce3500e31d6655df8557156c51c1cb874ce7"}, + {file = "mypy-1.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cfd13d47b29ed3bbaafaff7d8b21e90d827631afda134836962011acb5904b71"}, + {file = "mypy-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:eb4f18589d196a4cbe5290b435d135dee96567e07c2b2d43b5c4621b6501531a"}, + {file = "mypy-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:41697773aa0bf53ff917aa077e2cde7aa50254f28750f9b88884acea38a16169"}, + {file = "mypy-1.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7274b0c57737bd3476d2229c6389b2ec9eefeb090bbaf77777e9d6b1b5a9d143"}, + {file = "mypy-1.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbaf4662e498c8c2e352da5f5bca5ab29d378895fa2d980630656178bd607c46"}, + {file = "mypy-1.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bb8ccb4724f7d8601938571bf3f24da0da791fe2db7be3d9e79849cb64e0ae85"}, + {file = "mypy-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:68351911e85145f582b5aa6cd9ad666c8958bcae897a1bfda8f4940472463c45"}, + {file = "mypy-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:49ae115da099dcc0922a7a895c1eec82c1518109ea5c162ed50e3b3594c71208"}, + {file = "mypy-1.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b27958f8c76bed8edaa63da0739d76e4e9ad4ed325c814f9b3851425582a3cd"}, + {file = "mypy-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:925cd6a3b7b55dfba252b7c4561892311c5358c6b5a601847015a1ad4eb7d332"}, + {file = "mypy-1.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8f57e6b6927a49550da3d122f0cb983d400f843a8a82e65b3b380d3d7259468f"}, + {file = "mypy-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:a43ef1c8ddfdb9575691720b6352761f3f53d85f1b57d7745701041053deff30"}, + {file = "mypy-1.6.1-py3-none-any.whl", hash = "sha256:4cbe68ef919c28ea561165206a2dcb68591c50f3bcf777932323bc208d949cf1"}, + {file = "mypy-1.6.1.tar.gz", hash = "sha256:4d01c00d09a0be62a4ca3f933e315455bde83f37f892ba4b08ce92f3cf44bcc1"}, ] [package.dependencies] @@ -1461,7 +1402,6 @@ reports = ["lxml"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1471,28 +1411,26 @@ files = [ [[package]] name = "networkx" -version = "3.1" +version = "3.2" description = "Python package for creating and manipulating graphs and networks" -category = "main" optional = true -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "networkx-3.1-py3-none-any.whl", hash = "sha256:4f33f68cb2afcf86f28a45f43efc27a9386b535d567d2127f8f61d51dec58d36"}, - {file = "networkx-3.1.tar.gz", hash = "sha256:de346335408f84de0eada6ff9fafafff9bcda11f0a0dfaa931133debb146ab61"}, + {file = "networkx-3.2-py3-none-any.whl", hash = "sha256:8b25f564bd28f94ac821c58b04ae1a3109e73b001a7d476e4bb0d00d63706bf8"}, + {file = "networkx-3.2.tar.gz", hash = "sha256:bda29edf392d9bfa5602034c767d28549214ec45f620081f0b74dc036a1fbbc1"}, ] [package.extras] -default = ["matplotlib (>=3.4)", "numpy (>=1.20)", "pandas (>=1.3)", "scipy (>=1.8)"] -developer = ["mypy (>=1.1)", "pre-commit (>=3.2)"] -doc = ["nb2plots (>=0.6)", "numpydoc (>=1.5)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.13)", "sphinx (>=6.1)", "sphinx-gallery (>=0.12)", "texext (>=0.6.7)"] -extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.10)", "sympy (>=1.10)"] -test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] +default = ["matplotlib (>=3.5)", "numpy (>=1.22)", "pandas (>=1.4)", "scipy (>=1.9,!=1.11.0,!=1.11.1)"] +developer = ["changelist (==0.4)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] +doc = ["nb2plots (>=0.7)", "nbconvert (<7.9)", "numpydoc (>=1.6)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.14)", "sphinx (>=7)", "sphinx-gallery (>=0.14)", "texext (>=0.6.7)"] +extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.11)", "sympy (>=1.10)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] [[package]] name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -1505,29 +1443,32 @@ setuptools = "*" [[package]] name = "numcodecs" -version = "0.12.0" +version = "0.12.1" description = "A Python package providing buffer compression and transformation codecs for use in data storage and communication applications." -category = "main" optional = true python-versions = ">=3.8" files = [ - {file = "numcodecs-0.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e03960dd507e00bc102ff4ca2f14fa40b0cfc2ba7279752d31558d0787431a53"}, - {file = "numcodecs-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:68b3a18a93a96cba0a1d367ae76c02a74f29f93790e1c8b0423eacc4ce5d421a"}, - {file = "numcodecs-0.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a265db9177bd4a19939651b68722b72044bc92bb0b646e2a0d55835c0acb9d5"}, - {file = "numcodecs-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:3b5a0be940093d81eb49b0adba62615d3b973174d8167dbd63cc6d392e157bf6"}, - {file = "numcodecs-0.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f16787a674d1badd55f827b01bbc62b3ef2adecbed59a7db7139a328f0744e4a"}, - {file = "numcodecs-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98dbc61366e2974a1bdc28e08ed790c74d39c9cb40ce3f487ae6e6a76da843dd"}, - {file = "numcodecs-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd746cd6e7af4925bd2d3e902b5027147d71590cdc8e9e2ad999014fc2405c3b"}, - {file = "numcodecs-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:eae479f65b75af0e75a20049bf83beff154c4662a233695b4f7848d5eee0ef2d"}, - {file = "numcodecs-0.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c1f679b148bfdc9341686814485d03ad652ea551a90debadbbf9da3fb4cc003"}, - {file = "numcodecs-0.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:aa78ffc79a94aa78234821639c253219d8a26455f020c760ad1b331144363849"}, - {file = "numcodecs-0.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01f5457c0c81a556812240a2318c6022ca5c6f66fe2a51f619bdf8b0c855b5f2"}, - {file = "numcodecs-0.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:e9fc2f2abcb09c301c8e1db16e4d5dc9faf93be8c46d88ac3974e023f0a3533b"}, - {file = "numcodecs-0.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:98c2cedb3d6dd1238b033657da0b710689a9600813bfece28fd7c158328c0d4d"}, - {file = "numcodecs-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:182458355f5cad297575f9a16e804fe345c22c7a1b796ee9a0a8bce5a9f66c60"}, - {file = "numcodecs-0.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fe66c7a2b016e772a60dc8d68479958ae8c9ce306bcc318ee3d2ca883930e94"}, - {file = "numcodecs-0.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:488ba767d956f8dbf794c4c30df1983a385f048a4f1bc670dd0761b8fe7fd7a3"}, - {file = "numcodecs-0.12.0.tar.gz", hash = "sha256:6388e5f4e94d18a7165fbd1c9d3637673b74157cff8bc644005f9e2a4c717d6e"}, + {file = "numcodecs-0.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d37f628fe92b3699e65831d5733feca74d2e33b50ef29118ffd41c13c677210e"}, + {file = "numcodecs-0.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:941b7446b68cf79f089bcfe92edaa3b154533dcbcd82474f994b28f2eedb1c60"}, + {file = "numcodecs-0.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e79bf9d1d37199ac00a60ff3adb64757523291d19d03116832e600cac391c51"}, + {file = "numcodecs-0.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:82d7107f80f9307235cb7e74719292d101c7ea1e393fe628817f0d635b7384f5"}, + {file = "numcodecs-0.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eeaf42768910f1c6eebf6c1bb00160728e62c9343df9e2e315dc9fe12e3f6071"}, + {file = "numcodecs-0.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:135b2d47563f7b9dc5ee6ce3d1b81b0f1397f69309e909f1a35bb0f7c553d45e"}, + {file = "numcodecs-0.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a191a8e347ecd016e5c357f2bf41fbcb026f6ffe78fff50c77ab12e96701d155"}, + {file = "numcodecs-0.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:21d8267bd4313f4d16f5b6287731d4c8ebdab236038f29ad1b0e93c9b2ca64ee"}, + {file = "numcodecs-0.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:2f84df6b8693206365a5b37c005bfa9d1be486122bde683a7b6446af4b75d862"}, + {file = "numcodecs-0.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:760627780a8b6afdb7f942f2a0ddaf4e31d3d7eea1d8498cf0fd3204a33c4618"}, + {file = "numcodecs-0.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c258bd1d3dfa75a9b708540d23b2da43d63607f9df76dfa0309a7597d1de3b73"}, + {file = "numcodecs-0.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:e04649ea504aff858dbe294631f098fbfd671baf58bfc04fc48d746554c05d67"}, + {file = "numcodecs-0.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:caf1a1e6678aab9c1e29d2109b299f7a467bd4d4c34235b1f0e082167846b88f"}, + {file = "numcodecs-0.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c17687b1fd1fef68af616bc83f896035d24e40e04e91e7e6dae56379eb59fe33"}, + {file = "numcodecs-0.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29dfb195f835a55c4d490fb097aac8c1bcb96c54cf1b037d9218492c95e9d8c5"}, + {file = "numcodecs-0.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:2f1ba2f4af3fd3ba65b1bcffb717fe65efe101a50a91c368f79f3101dbb1e243"}, + {file = "numcodecs-0.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2fbb12a6a1abe95926f25c65e283762d63a9bf9e43c0de2c6a1a798347dfcb40"}, + {file = "numcodecs-0.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f2207871868b2464dc11c513965fd99b958a9d7cde2629be7b2dc84fdaab013b"}, + {file = "numcodecs-0.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abff3554a6892a89aacf7b642a044e4535499edf07aeae2f2e6e8fc08c9ba07f"}, + {file = "numcodecs-0.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:ef964d4860d3e6b38df0633caf3e51dc850a6293fd8e93240473642681d95136"}, + {file = "numcodecs-0.12.1.tar.gz", hash = "sha256:05d91a433733e7eef268d7e80ec226a0232da244289614a8f3826901aec1098e"}, ] [package.dependencies] @@ -1537,55 +1478,54 @@ numpy = ">=1.7" docs = ["mock", "numpydoc", "sphinx (<7.0.0)", "sphinx-issues"] msgpack = ["msgpack"] test = ["coverage", "flake8", "pytest", "pytest-cov"] +test-extras = ["importlib-metadata"] zfpy = ["zfpy (>=1.0.0)"] [[package]] name = "numpy" -version = "1.26.0" +version = "1.26.1" description = "Fundamental package for array computing in Python" -category = "main" optional = false python-versions = "<3.13,>=3.9" files = [ - {file = "numpy-1.26.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f8db2f125746e44dce707dd44d4f4efeea8d7e2b43aace3f8d1f235cfa2733dd"}, - {file = "numpy-1.26.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0621f7daf973d34d18b4e4bafb210bbaf1ef5e0100b5fa750bd9cde84c7ac292"}, - {file = "numpy-1.26.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51be5f8c349fdd1a5568e72713a21f518e7d6707bcf8503b528b88d33b57dc68"}, - {file = "numpy-1.26.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:767254ad364991ccfc4d81b8152912e53e103ec192d1bb4ea6b1f5a7117040be"}, - {file = "numpy-1.26.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:436c8e9a4bdeeee84e3e59614d38c3dbd3235838a877af8c211cfcac8a80b8d3"}, - {file = "numpy-1.26.0-cp310-cp310-win32.whl", hash = "sha256:c2e698cb0c6dda9372ea98a0344245ee65bdc1c9dd939cceed6bb91256837896"}, - {file = "numpy-1.26.0-cp310-cp310-win_amd64.whl", hash = "sha256:09aaee96c2cbdea95de76ecb8a586cb687d281c881f5f17bfc0fb7f5890f6b91"}, - {file = "numpy-1.26.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:637c58b468a69869258b8ae26f4a4c6ff8abffd4a8334c830ffb63e0feefe99a"}, - {file = "numpy-1.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:306545e234503a24fe9ae95ebf84d25cba1fdc27db971aa2d9f1ab6bba19a9dd"}, - {file = "numpy-1.26.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c6adc33561bd1d46f81131d5352348350fc23df4d742bb246cdfca606ea1208"}, - {file = "numpy-1.26.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e062aa24638bb5018b7841977c360d2f5917268d125c833a686b7cbabbec496c"}, - {file = "numpy-1.26.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:546b7dd7e22f3c6861463bebb000646fa730e55df5ee4a0224408b5694cc6148"}, - {file = "numpy-1.26.0-cp311-cp311-win32.whl", hash = "sha256:c0b45c8b65b79337dee5134d038346d30e109e9e2e9d43464a2970e5c0e93229"}, - {file = "numpy-1.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:eae430ecf5794cb7ae7fa3808740b015aa80747e5266153128ef055975a72b99"}, - {file = "numpy-1.26.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:166b36197e9debc4e384e9c652ba60c0bacc216d0fc89e78f973a9760b503388"}, - {file = "numpy-1.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f042f66d0b4ae6d48e70e28d487376204d3cbf43b84c03bac57e28dac6151581"}, - {file = "numpy-1.26.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5e18e5b14a7560d8acf1c596688f4dfd19b4f2945b245a71e5af4ddb7422feb"}, - {file = "numpy-1.26.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f6bad22a791226d0a5c7c27a80a20e11cfe09ad5ef9084d4d3fc4a299cca505"}, - {file = "numpy-1.26.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4acc65dd65da28060e206c8f27a573455ed724e6179941edb19f97e58161bb69"}, - {file = "numpy-1.26.0-cp312-cp312-win32.whl", hash = "sha256:bb0d9a1aaf5f1cb7967320e80690a1d7ff69f1d47ebc5a9bea013e3a21faec95"}, - {file = "numpy-1.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:ee84ca3c58fe48b8ddafdeb1db87388dce2c3c3f701bf447b05e4cfcc3679112"}, - {file = "numpy-1.26.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4a873a8180479bc829313e8d9798d5234dfacfc2e8a7ac188418189bb8eafbd2"}, - {file = "numpy-1.26.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:914b28d3215e0c721dc75db3ad6d62f51f630cb0c277e6b3bcb39519bed10bd8"}, - {file = "numpy-1.26.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c78a22e95182fb2e7874712433eaa610478a3caf86f28c621708d35fa4fd6e7f"}, - {file = "numpy-1.26.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86f737708b366c36b76e953c46ba5827d8c27b7a8c9d0f471810728e5a2fe57c"}, - {file = "numpy-1.26.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b44e6a09afc12952a7d2a58ca0a2429ee0d49a4f89d83a0a11052da696440e49"}, - {file = "numpy-1.26.0-cp39-cp39-win32.whl", hash = "sha256:5671338034b820c8d58c81ad1dafc0ed5a00771a82fccc71d6438df00302094b"}, - {file = "numpy-1.26.0-cp39-cp39-win_amd64.whl", hash = "sha256:020cdbee66ed46b671429c7265cf00d8ac91c046901c55684954c3958525dab2"}, - {file = "numpy-1.26.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0792824ce2f7ea0c82ed2e4fecc29bb86bee0567a080dacaf2e0a01fe7654369"}, - {file = "numpy-1.26.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d484292eaeb3e84a51432a94f53578689ffdea3f90e10c8b203a99be5af57d8"}, - {file = "numpy-1.26.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:186ba67fad3c60dbe8a3abff3b67a91351100f2661c8e2a80364ae6279720299"}, - {file = "numpy-1.26.0.tar.gz", hash = "sha256:f93fc78fe8bf15afe2b8d6b6499f1c73953169fad1e9a8dd086cdff3190e7fdf"}, + {file = "numpy-1.26.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82e871307a6331b5f09efda3c22e03c095d957f04bf6bc1804f30048d0e5e7af"}, + {file = "numpy-1.26.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdd9ec98f0063d93baeb01aad472a1a0840dee302842a2746a7a8e92968f9575"}, + {file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d78f269e0c4fd365fc2992c00353e4530d274ba68f15e968d8bc3c69ce5f5244"}, + {file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ab9163ca8aeb7fd32fe93866490654d2f7dda4e61bc6297bf72ce07fdc02f67"}, + {file = "numpy-1.26.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:78ca54b2f9daffa5f323f34cdf21e1d9779a54073f0018a3094ab907938331a2"}, + {file = "numpy-1.26.1-cp310-cp310-win32.whl", hash = "sha256:d1cfc92db6af1fd37a7bb58e55c8383b4aa1ba23d012bdbba26b4bcca45ac297"}, + {file = "numpy-1.26.1-cp310-cp310-win_amd64.whl", hash = "sha256:d2984cb6caaf05294b8466966627e80bf6c7afd273279077679cb010acb0e5ab"}, + {file = "numpy-1.26.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd7837b2b734ca72959a1caf3309457a318c934abef7a43a14bb984e574bbb9a"}, + {file = "numpy-1.26.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c59c046c31a43310ad0199d6299e59f57a289e22f0f36951ced1c9eac3665b9"}, + {file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d58e8c51a7cf43090d124d5073bc29ab2755822181fcad978b12e144e5e5a4b3"}, + {file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6081aed64714a18c72b168a9276095ef9155dd7888b9e74b5987808f0dd0a974"}, + {file = "numpy-1.26.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:97e5d6a9f0702c2863aaabf19f0d1b6c2628fbe476438ce0b5ce06e83085064c"}, + {file = "numpy-1.26.1-cp311-cp311-win32.whl", hash = "sha256:b9d45d1dbb9de84894cc50efece5b09939752a2d75aab3a8b0cef6f3a35ecd6b"}, + {file = "numpy-1.26.1-cp311-cp311-win_amd64.whl", hash = "sha256:3649d566e2fc067597125428db15d60eb42a4e0897fc48d28cb75dc2e0454e53"}, + {file = "numpy-1.26.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d1bd82d539607951cac963388534da3b7ea0e18b149a53cf883d8f699178c0f"}, + {file = "numpy-1.26.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:afd5ced4e5a96dac6725daeb5242a35494243f2239244fad10a90ce58b071d24"}, + {file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03fb25610ef560a6201ff06df4f8105292ba56e7cdd196ea350d123fc32e24e"}, + {file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcfaf015b79d1f9f9c9fd0731a907407dc3e45769262d657d754c3a028586124"}, + {file = "numpy-1.26.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e509cbc488c735b43b5ffea175235cec24bbc57b227ef1acc691725beb230d1c"}, + {file = "numpy-1.26.1-cp312-cp312-win32.whl", hash = "sha256:af22f3d8e228d84d1c0c44c1fbdeb80f97a15a0abe4f080960393a00db733b66"}, + {file = "numpy-1.26.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f42284ebf91bdf32fafac29d29d4c07e5e9d1af862ea73686581773ef9e73a7"}, + {file = "numpy-1.26.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb894accfd16b867d8643fc2ba6c8617c78ba2828051e9a69511644ce86ce83e"}, + {file = "numpy-1.26.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e44ccb93f30c75dfc0c3aa3ce38f33486a75ec9abadabd4e59f114994a9c4617"}, + {file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9696aa2e35cc41e398a6d42d147cf326f8f9d81befcb399bc1ed7ffea339b64e"}, + {file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5b411040beead47a228bde3b2241100454a6abde9df139ed087bd73fc0a4908"}, + {file = "numpy-1.26.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1e11668d6f756ca5ef534b5be8653d16c5352cbb210a5c2a79ff288e937010d5"}, + {file = "numpy-1.26.1-cp39-cp39-win32.whl", hash = "sha256:d1d2c6b7dd618c41e202c59c1413ef9b2c8e8a15f5039e344af64195459e3104"}, + {file = "numpy-1.26.1-cp39-cp39-win_amd64.whl", hash = "sha256:59227c981d43425ca5e5c01094d59eb14e8772ce6975d4b2fc1e106a833d5ae2"}, + {file = "numpy-1.26.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:06934e1a22c54636a059215d6da99e23286424f316fddd979f5071093b648668"}, + {file = "numpy-1.26.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76ff661a867d9272cd2a99eed002470f46dbe0943a5ffd140f49be84f68ffc42"}, + {file = "numpy-1.26.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6965888d65d2848e8768824ca8288db0a81263c1efccec881cb35a0d805fcd2f"}, + {file = "numpy-1.26.1.tar.gz", hash = "sha256:c8c6c72d4a9f831f328efb1312642a1cafafaa88981d9ab76368d50d07d93cbe"}, ] [[package]] name = "packaging" version = "23.2" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1597,7 +1537,6 @@ files = [ name = "pandas" version = "2.1.1" description = "Powerful data structures for data analysis, time series, and statistics" -category = "main" optional = false python-versions = ">=3.9" files = [ @@ -1665,7 +1604,6 @@ xml = ["lxml (>=4.8.0)"] name = "paramiko" version = "3.3.1" description = "SSH2 protocol library" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1687,7 +1625,6 @@ invoke = ["invoke (>=2.0)"] name = "partd" version = "1.4.1" description = "Appendable key-value storage" -category = "main" optional = true python-versions = ">=3.7" files = [ @@ -1706,7 +1643,6 @@ complete = ["blosc", "numpy (>=1.9.0)", "pandas (>=0.19.0)", "pyzmq"] name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1718,7 +1654,6 @@ files = [ name = "photutils" version = "1.9.0" description = "An Astropy package for source detection and photometry" -category = "main" optional = true python-versions = ">=3.9" files = [ @@ -1750,7 +1685,6 @@ test = ["pytest-astropy (>=0.10)"] name = "pillow" version = "10.0.1" description = "Python Imaging Library (Fork)" -category = "main" optional = true python-versions = ">=3.8" files = [ @@ -1818,7 +1752,6 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa name = "platformdirs" version = "3.11.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1834,7 +1767,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1848,14 +1780,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.4.0" +version = "3.5.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "pre_commit-3.4.0-py2.py3-none-any.whl", hash = "sha256:96d529a951f8b677f730a7212442027e8ba53f9b04d217c4c67dc56c393ad945"}, - {file = "pre_commit-3.4.0.tar.gz", hash = "sha256:6bbd5129a64cad4c0dfaeeb12cd8f7ea7e15b77028d985341478c8af3c759522"}, + {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"}, + {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"}, ] [package.dependencies] @@ -1869,7 +1800,6 @@ virtualenv = ">=20.10.0" name = "py-expression-eval" version = "0.3.14" description = "Python Mathematical Expression Evaluator" -category = "main" optional = false python-versions = "*" files = [ @@ -1881,7 +1811,6 @@ files = [ name = "pyasn1" version = "0.5.0" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -1893,7 +1822,6 @@ files = [ name = "pyasn1-modules" version = "0.3.0" description = "A collection of ASN.1-based protocols modules" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ @@ -1908,7 +1836,6 @@ pyasn1 = ">=0.4.6,<0.6.0" name = "pycares" version = "4.4.0" description = "Python interface for c-ares" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1975,7 +1902,6 @@ idna = ["idna (>=2.1)"] name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1985,56 +1911,26 @@ files = [ [[package]] name = "pyerfa" -version = "2.0.0.3" +version = "2.0.1.1" description = "Python bindings for ERFA" -category = "main" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "pyerfa-2.0.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:676515861ca3f0cb9d7e693389233e7126413a5ba93a0cc4d36b8ca933951e8d"}, - {file = "pyerfa-2.0.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a438865894d226247dcfcb60d683ae075a52716504537052371b2b73458fe4fc"}, - {file = "pyerfa-2.0.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73bf7d23f069d47632a2feeb1e73454b10392c4f3c16116017a6983f1f0e9b2b"}, - {file = "pyerfa-2.0.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:780b0f90adf500b8ba24e9d509a690576a7e8287e354cfb90227c5963690d3fc"}, - {file = "pyerfa-2.0.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5447bb45ddedde3052693c86b941a4908f5dbeb4a697bda45b5b89de92cfb74a"}, - {file = "pyerfa-2.0.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7c24e7960c6cdd3fa3f4dba5f3444a106ad48c94ff0b19eebaee06a142c18c52"}, - {file = "pyerfa-2.0.0.3-cp310-cp310-win32.whl", hash = "sha256:170a83bd0243da518119b846f296cf33fa03f1f884a88578c1a38560182cf64e"}, - {file = "pyerfa-2.0.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:51aa6e0faa4aa9ad8f0eef1c47fec76c5bebc0da7023a436089bdd6e5cfd625f"}, - {file = "pyerfa-2.0.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4fa9fceeb78057bfff7ae3aa6cdad3f1b193722de22bdbb75319256f4a9e2f76"}, - {file = "pyerfa-2.0.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a8a2029fc62ff2369d01219f66a5ce6aed35ef33eddb06118b6c27e8573a9ed8"}, - {file = "pyerfa-2.0.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da888da2c8db5a78273fbf0af4e74f04e2d312d371c3c021cf6c3b14fa60fe3b"}, - {file = "pyerfa-2.0.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7354753addba5261ec1cbf1ba45784ed3a5c42da565ecc6e0aa36b7a17fa4689"}, - {file = "pyerfa-2.0.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b55f7278c1dd362648d7956e1a5365ade5fed2fe5541b721b3ceb5271128892"}, - {file = "pyerfa-2.0.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:23e5efcf96ed7161d74f79ca261d255e1f36988843d22cd97d8f60fe9c868d44"}, - {file = "pyerfa-2.0.0.3-cp311-cp311-win32.whl", hash = "sha256:f0e9d0b122c454bcad5dbd0c3283b200783031d3f99ca9c550f49a7a7d4c41ea"}, - {file = "pyerfa-2.0.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:09af83540e23a7d61a8368b0514b3daa4ed967e1e52d0add4f501f58c500dd7f"}, - {file = "pyerfa-2.0.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a07444fd53a5dd18d7955f86f8d9b1be9a68ceb143e1145c0019a310c913c04"}, - {file = "pyerfa-2.0.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf7364e475cff1f973e2fcf6962de9df9642c8802b010e29b2c592ae337e3c5"}, - {file = "pyerfa-2.0.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8458421166f6ffe2e259aaf4aaa6e802d6539649a40e3194a81d30dccdc167a"}, - {file = "pyerfa-2.0.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96ea688341176ae6220cc4743cda655549d71e3e3b60c5a99d02d5912d0ddf55"}, - {file = "pyerfa-2.0.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d56f6b5a0a3ed7b80d630041829463a872946df277259b5453298842d42a54a4"}, - {file = "pyerfa-2.0.0.3-cp37-cp37m-win32.whl", hash = "sha256:3ecb598924ddb4ea2b06efc6f1e55ca70897ed178a690e2eaa1e290448466c7c"}, - {file = "pyerfa-2.0.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:1033fdb890ec70d3a511e20a464afc8abbea2180108f27b14d8f1d1addc38cbe"}, - {file = "pyerfa-2.0.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d8c0dbb17119e52def33f9d6dbf2deaf2113ed3e657b6ff692df9b6a3598397"}, - {file = "pyerfa-2.0.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8a1edd2cbe4ead3bf9a51e578d5d83bdd7ab3b3ccb69e09b89a4c42aa5b35ffb"}, - {file = "pyerfa-2.0.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a04c3b715c924b6f972dd440a94a701a16a07700bc8ba9e88b1df765bdc36ad0"}, - {file = "pyerfa-2.0.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d01c341c45b860ee5c7585ef003118c8015e9d65c30668d2f5bf657e1dcdd68"}, - {file = "pyerfa-2.0.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24d89ead30edc6038408336ad9b696683e74c4eef550708fca6afef3ecd5b010"}, - {file = "pyerfa-2.0.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0b8c5e74d48a505a014e855cd4c7be11604901d94fd6f34b685f6720b7b20ed8"}, - {file = "pyerfa-2.0.0.3-cp38-cp38-win32.whl", hash = "sha256:2ccba04de166d81bdd3adcf10428d908ce2f3a56ed1c2767d740fec12680edbd"}, - {file = "pyerfa-2.0.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:3df87743e27588c5bd5e1f3a886629b3277fdd418059ca048420d33169376775"}, - {file = "pyerfa-2.0.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:88aa1acedf298d255cc4b0740ee11a3b303b71763dba2f039d48abf0a95cf9df"}, - {file = "pyerfa-2.0.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06d4f08e96867b1fc3ae9a9e4b38693ed0806463288efc41473ad16e14774504"}, - {file = "pyerfa-2.0.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1819e0d95ff8dead80614f8063919d82b2dbb55437b6c0109d3393c1ab55954"}, - {file = "pyerfa-2.0.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61f1097ac2ee8c15a2a636cdfb99340d708574d66f4610456bd457d1e6b852f4"}, - {file = "pyerfa-2.0.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36f42ee01a62c6cbba58103e6f8e600b21ad3a71262dccf03d476efb4a20ea71"}, - {file = "pyerfa-2.0.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3ecd6167b48bb8f1922fae7b49554616f2e7382748a4320ad46ebd7e2cc62f3d"}, - {file = "pyerfa-2.0.0.3-cp39-cp39-win32.whl", hash = "sha256:7f9eabfefa5317ce58fe22480102902f10f270fc64a5636c010f7c0b7e0fb032"}, - {file = "pyerfa-2.0.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:4ea7ca03ecc440224c2bed8fb136fadf6cf8aea8ba67d717f635116f30c8cc8c"}, - {file = "pyerfa-2.0.0.3.tar.gz", hash = "sha256:d77fbbfa58350c194ccb99e5d93aa05d3c2b14d5aad8b662d93c6ad9fff41f39"}, + {file = "pyerfa-2.0.1.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:1ce322ac30673c2aeb0ee22ced4938c1e9e26db0cbe175912a213aaff42383df"}, + {file = "pyerfa-2.0.1.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:67dfc00dcdea87a9b3c0bb4596fb0cfb54ee9c1c75fdcf19411d1029a18f6eec"}, + {file = "pyerfa-2.0.1.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34ee545780246fb0d1d3f7e46a6daa152be06a26b2d27fbfe309cab9ab488ea7"}, + {file = "pyerfa-2.0.1.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db85db72ab352da6ffc790e41209d8f41feb5b175d682cf1f0e3e60e9e5cdf8"}, + {file = "pyerfa-2.0.1.1-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c50b7cdb005632931b7b56a679cf25361ed6b3aa7c21e491e65cc89cb337e66a"}, + {file = "pyerfa-2.0.1.1-cp39-abi3-win32.whl", hash = "sha256:30649047b7a8ce19f43e4d18a26b8a44405a6bb406df16c687330a3b937723b2"}, + {file = "pyerfa-2.0.1.1-cp39-abi3-win_amd64.whl", hash = "sha256:94df7566ce5a5abb14e2dd1fe879204390639e9a76383ec27f10598eb24be760"}, + {file = "pyerfa-2.0.1.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0e95cf3d11f76f473bf011980e9ea367ca7e68ca675d8b32499814fb6e387d4c"}, + {file = "pyerfa-2.0.1.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08b5abb90b34e819c1fca69047a76c0d344cb0c8fe4f7c8773f032d8afd623b4"}, + {file = "pyerfa-2.0.1.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1c0c1efa701cab986aa58d03c58f77e47ea1898bff2684377d29580a055f836a"}, + {file = "pyerfa-2.0.1.1.tar.gz", hash = "sha256:dbac74ef8d3d3b0f22ef0ad3bbbdb30b2a9e10570b1fa5a98be34c7be36c9a6b"}, ] [package.dependencies] -numpy = ">=1.17" +numpy = ">=1.19" [package.extras] docs = ["sphinx-astropy (>=1.3)"] @@ -2044,7 +1940,6 @@ test = ["pytest", "pytest-doctestplus (>=0.7)"] name = "pynacl" version = "1.5.0" description = "Python binding to the Networking and Cryptography (NaCl) library" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2071,7 +1966,6 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] name = "pytest" version = "7.4.2" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2094,7 +1988,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-asyncio" version = "0.21.1" description = "Pytest support for asyncio" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2109,11 +2002,27 @@ pytest = ">=7.0.0" docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"] +[[package]] +name = "pytest-mock" +version = "3.12.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-mock-3.12.0.tar.gz", hash = "sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9"}, + {file = "pytest_mock-3.12.0-py3-none-any.whl", hash = "sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f"}, +] + +[package.dependencies] +pytest = ">=5.0" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "python-daemon" version = "3.0.1" description = "Library to implement a well-behaved Unix daemon process." -category = "main" optional = false python-versions = ">=3" files = [ @@ -2134,7 +2043,6 @@ test = ["coverage", "docutils", "testscenarios (>=0.4)", "testtools"] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -2149,7 +2057,6 @@ six = ">=1.5" name = "python-telegram-bot" version = "20.6" description = "We have made you a wrapper you can't refuse" -category = "main" optional = true python-versions = ">=3.8" files = [ @@ -2175,7 +2082,6 @@ webhooks = ["tornado (>=6.3.3,<6.4.0)"] name = "pytz" version = "2023.3.post1" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -2187,7 +2093,6 @@ files = [ name = "pyvo" version = "1.4.2" description = "Astropy affiliated package for accessing Virtual Observatory data and services" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2208,7 +2113,6 @@ test = ["pytest-astropy", "pytest-doctestplus (>=0.13)", "requests-mock"] name = "pywin32-ctypes" version = "0.2.2" description = "A (partial) reimplementation of pywin32 using ctypes/cffi" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2220,7 +2124,6 @@ files = [ name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2229,6 +2132,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -2236,8 +2140,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -2254,6 +2165,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -2261,6 +2173,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -2270,7 +2183,6 @@ files = [ name = "reproject" version = "0.12.0" description = "Reproject astronomical images" -category = "main" optional = true python-versions = ">=3.8" files = [ @@ -2317,7 +2229,6 @@ testall = ["asdf", "gwcs", "pyvo", "shapely", "sunpy[map] (>=2.1)"] name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2339,7 +2250,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "scikit-image" version = "0.22.0" description = "Image processing in Python" -category = "main" optional = true python-versions = ">=3.9" files = [ @@ -2388,7 +2298,6 @@ test = ["asv", "matplotlib (>=3.5)", "numpydoc (>=1.5)", "pooch (>=1.6.0)", "pyt name = "scipy" version = "1.11.3" description = "Fundamental algorithms for scientific computing in Python" -category = "main" optional = false python-versions = "<3.13,>=3.9" files = [ @@ -2431,7 +2340,6 @@ test = ["asv", "gmpy2", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeo name = "secretstorage" version = "3.3.3" description = "Python bindings to FreeDesktop.org Secret Service API" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2447,7 +2355,6 @@ jeepney = ">=0.6" name = "sep" version = "1.2.1" description = "Astronomical source extraction and photometry library" -category = "main" optional = true python-versions = ">=3.5" files = [ @@ -2497,7 +2404,6 @@ numpy = "*" name = "setuptools" version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2514,7 +2420,6 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar name = "single-source" version = "0.3.0" description = "Access to the project version in Python code for PEP 621-style projects" -category = "main" optional = false python-versions = ">=3.6,<4.0" files = [ @@ -2526,7 +2431,6 @@ files = [ name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -2538,7 +2442,6 @@ files = [ name = "slixmpp" version = "1.8.4" description = "Slixmpp is an elegant Python library for XMPP (aka Jabber)." -category = "main" optional = false python-versions = "*" files = [ @@ -2560,7 +2463,6 @@ xep-0454 = ["cryptography"] name = "sniffio" version = "1.3.0" description = "Sniff out which async library your code is running under" -category = "main" optional = true python-versions = ">=3.7" files = [ @@ -2572,7 +2474,6 @@ files = [ name = "soupsieve" version = "2.5" description = "A modern CSS selector implementation for Beautiful Soup." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2584,7 +2485,6 @@ files = [ name = "tifffile" version = "2023.9.26" description = "Read and write TIFF files" -category = "main" optional = true python-versions = ">=3.9" files = [ @@ -2602,7 +2502,6 @@ all = ["defusedxml", "fsspec", "imagecodecs (>=2023.8.12)", "lxml", "matplotlib" name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2614,7 +2513,6 @@ files = [ name = "toolz" version = "0.12.0" description = "List processing tools and functional utilities" -category = "main" optional = true python-versions = ">=3.5" files = [ @@ -2626,7 +2524,6 @@ files = [ name = "tornado" version = "6.3.3" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." -category = "main" optional = true python-versions = ">= 3.8" files = [ @@ -2647,7 +2544,6 @@ files = [ name = "types-cryptography" version = "3.3.23.2" description = "Typing stubs for cryptography" -category = "dev" optional = false python-versions = "*" files = [ @@ -2659,7 +2555,6 @@ files = [ name = "types-enum34" version = "1.1.8" description = "Typing stubs for enum34" -category = "dev" optional = false python-versions = "*" files = [ @@ -2671,7 +2566,6 @@ files = [ name = "types-ipaddress" version = "1.0.8" description = "Typing stubs for ipaddress" -category = "dev" optional = false python-versions = "*" files = [ @@ -2683,7 +2577,6 @@ files = [ name = "types-paramiko" version = "3.3.0.0" description = "Typing stubs for paramiko" -category = "dev" optional = false python-versions = "*" files = [ @@ -2698,7 +2591,6 @@ cryptography = ">=37.0.0" name = "types-pytz" version = "2023.3.1.1" description = "Typing stubs for pytz" -category = "dev" optional = false python-versions = "*" files = [ @@ -2710,7 +2602,6 @@ files = [ name = "types-pyyaml" version = "6.0.12.12" description = "Typing stubs for PyYAML" -category = "dev" optional = false python-versions = "*" files = [ @@ -2720,14 +2611,13 @@ files = [ [[package]] name = "types-requests" -version = "2.31.0.8" +version = "2.31.0.10" description = "Typing stubs for requests" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "types-requests-2.31.0.8.tar.gz", hash = "sha256:e1b325c687b3494a2f528ab06e411d7092cc546cc9245c000bacc2fca5ae96d4"}, - {file = "types_requests-2.31.0.8-py3-none-any.whl", hash = "sha256:39894cbca3fb3d032ed8bdd02275b4273471aa5668564617cc1734b0a65ffdf8"}, + {file = "types-requests-2.31.0.10.tar.gz", hash = "sha256:dc5852a76f1eaf60eafa81a2e50aefa3d1f015c34cf0cba130930866b1b22a92"}, + {file = "types_requests-2.31.0.10-py3-none-any.whl", hash = "sha256:b32b9a86beffa876c0c3ac99a4cd3b8b51e973fb8e3bd4e0a6bb32c7efad80fc"}, ] [package.dependencies] @@ -2737,7 +2627,6 @@ urllib3 = ">=2" name = "types-setuptools" version = "68.2.0.0" description = "Typing stubs for setuptools" -category = "dev" optional = false python-versions = "*" files = [ @@ -2749,7 +2638,6 @@ files = [ name = "types-toml" version = "0.10.8.7" description = "Typing stubs for toml" -category = "dev" optional = false python-versions = "*" files = [ @@ -2761,7 +2649,6 @@ files = [ name = "types-tornado" version = "5.1.1" description = "Typing stubs for tornado" -category = "dev" optional = false python-versions = "*" files = [ @@ -2773,7 +2660,6 @@ files = [ name = "typing-extensions" version = "4.8.0" description = "Backported and Experimental Type Hints for Python 3.8+" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2785,7 +2671,6 @@ files = [ name = "tzdata" version = "2023.3" description = "Provider of IANA time zone data" -category = "main" optional = false python-versions = ">=2" files = [ @@ -2797,7 +2682,6 @@ files = [ name = "uncertainties" version = "3.1.7" description = "Transparent calculations with uncertainties on the quantities involved (aka error propagation); fast calculation of derivatives" -category = "main" optional = true python-versions = "*" files = [ @@ -2816,14 +2700,13 @@ tests = ["nose", "numpy"] [[package]] name = "urllib3" -version = "2.0.6" +version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.6-py3-none-any.whl", hash = "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2"}, - {file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"}, + {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, + {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, ] [package.extras] @@ -2834,14 +2717,13 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.24.5" +version = "20.24.6" description = "Virtual Python Environment builder" -category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"}, - {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"}, + {file = "virtualenv-20.24.6-py3-none-any.whl", hash = "sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381"}, + {file = "virtualenv-20.24.6.tar.gz", hash = "sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af"}, ] [package.dependencies] @@ -2857,7 +2739,6 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess name = "webencodings" version = "0.5.1" description = "Character encoding aliases for legacy web content" -category = "main" optional = false python-versions = "*" files = [ @@ -2869,7 +2750,6 @@ files = [ name = "yarl" version = "1.9.2" description = "Yet another URL library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2957,7 +2837,6 @@ multidict = ">=4.0" name = "zarr" version = "2.16.1" description = "An implementation of chunked, compressed, N-dimensional arrays for Python" -category = "main" optional = true python-versions = ">=3.8" files = [ @@ -2979,7 +2858,6 @@ jupyter = ["ipytree (>=0.2.2)", "ipywidgets (>=8.0.0)", "notebook"] name = "zipp" version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2997,4 +2875,4 @@ full = ["aiohttp", "asyncinotify", "ccdproc", "lmfit", "pandas", "paramiko", "ph [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "121c06fdb7d43b6bdd8cde5900617a096e1922fe6a32910c7769abaacee9261a" +content-hash = "8d2342e2f5d9448cc4a4462b5373800d0b6fb982046453dfe24a0090792d2486" diff --git a/pyproject.toml b/pyproject.toml index 1558c95d..0ee58325 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,7 @@ types-tornado = "^5.1.1" mypy = "^1.5.1" pytest = "^7.4.2" pytest-asyncio = "^0.21.1" +pytest-mock = "^3.12.0" black = "^23.9.1" pre-commit = "^3.4.0" diff --git a/tests/comm/__init__.py b/tests/comm/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/comm/local/__init__.py b/tests/comm/local/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/comm/local/test_localcomm.py b/tests/comm/local/test_localcomm.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/comm/local/test_localnetwork.py b/tests/comm/local/test_localnetwork.py new file mode 100644 index 00000000..e69de29b From 66443dd2a1aa7ca6647e3c0cae6980cacefbeba2 Mon Sep 17 00:00:00 2001 From: GermanHydrogen Date: Tue, 24 Oct 2023 09:22:56 +0200 Subject: [PATCH 08/22] Added unit tests to local comm and network --- pyobs/comm/local/localcomm.py | 13 ++-- pyobs/comm/local/localnetwork.py | 4 +- tests/comm/local/test_localcomm.py | 93 +++++++++++++++++++++++++++ tests/comm/local/test_localnetwork.py | 44 +++++++++++++ 4 files changed, 147 insertions(+), 7 deletions(-) diff --git a/pyobs/comm/local/localcomm.py b/pyobs/comm/local/localcomm.py index bebe1af7..fd013ae0 100644 --- a/pyobs/comm/local/localcomm.py +++ b/pyobs/comm/local/localcomm.py @@ -5,6 +5,7 @@ from pyobs.comm import Comm from pyobs.events import Event from pyobs.interfaces import Interface +from pyobs.utils.types import cast_response_to_real class LocalComm(Comm): @@ -14,7 +15,7 @@ def __init__(self, name: str, *args, **kwargs): self._name = name self._network = pyobs.comm.local.LocalNetwork() - self._network.connect(name, self) + self._network.connect_client(self) @property def name(self) -> Optional[str]: @@ -42,11 +43,9 @@ async def get_interfaces(self, client: str) -> List[Type[Interface]]: Raises: IndexError: If client cannot be found. """ - if client == self.name: - return self.module.interfaces remote_client: LocalComm = self._network.get_client(client) - return await remote_client.get_interfaces(client) + return remote_client.module.interfaces async def _supports_interface(self, client: str, interface: Type[Interface]) -> bool: """Checks, whether the given client supports the given interface. @@ -75,7 +74,11 @@ async def execute(self, client: str, method: str, annotation: Dict[str, Any], *a """ remote_client = self._network.get_client(client) - return await remote_client.module.execute(method, *args) + simple_results = await remote_client.module.execute(method, *args) + real_results = cast_response_to_real( + simple_results, annotation["return"], self.cast_to_real_pre, self.cast_to_real_post + ) + return real_results async def send_event(self, event: Event) -> None: """Send an event to other clients. diff --git a/pyobs/comm/local/localnetwork.py b/pyobs/comm/local/localnetwork.py index adde274d..9af6ed54 100644 --- a/pyobs/comm/local/localnetwork.py +++ b/pyobs/comm/local/localnetwork.py @@ -19,8 +19,8 @@ def __new__(cls): return cls._instance - def connect(self, name: str, comm: pyobs.comm.local.LocalComm): - self._clients[name] = comm + def connect_client(self, comm: pyobs.comm.local.LocalComm): + self._clients[comm.name] = comm def get_client(self, name: str) -> pyobs.comm.local.LocalComm: return self._clients[name] diff --git a/tests/comm/local/test_localcomm.py b/tests/comm/local/test_localcomm.py index e69de29b..ab6788b2 100644 --- a/tests/comm/local/test_localcomm.py +++ b/tests/comm/local/test_localcomm.py @@ -0,0 +1,93 @@ +import pytest +from typing import Any + +from pyobs.comm.local import LocalNetwork, LocalComm +from pyobs.events import Event, GoodWeatherEvent +from pyobs.interfaces import ICamera, IExposureTime, IImageType, IModule, IConfig +from pyobs.mixins import ImageFitsHeaderMixin +from pyobs.modules import Module +from pyobs.modules.camera import BaseCamera +from pyobs.utils.enums import ImageType + + +def test_init(): + comm = LocalComm("test") + + assert comm._name == "test" + assert comm._network.get_client("test") == comm + + +def test_name(): + comm = LocalComm("test") + + assert comm.name == "test" + + +def test_clients(mocker): + comm = LocalComm("test") + + clients = ["test", "telescope", "camera"] + mocker.patch.object(comm._network, "get_client_names", return_value=clients) + + assert comm.clients == clients + + +class TestModule(Module, IImageType): + + async def set_image_type(self, image_type: ImageType, **kwargs: Any) -> None: + pass + + async def get_image_type(self, **kwargs: Any) -> ImageType: + return ImageType.BIAS + + +@pytest.mark.asyncio +async def test_get_interfaces(mocker): + comm = LocalComm("test") + + another_comm = LocalComm("camera") + another_comm.module = TestModule() + + mocker.patch.object(comm._network, "get_client", return_value=another_comm) + + interfaces = await comm.get_interfaces("camera") + + assert set(interfaces) == {IConfig, IImageType, IModule} + comm._network.get_client.assert_called_once_with("camera") + + +@pytest.mark.asyncio +async def test_supports_interface(mocker): + comm = LocalComm("test") + + mocker.patch.object(comm, "get_interfaces", return_value=[IConfig, IImageType, IModule]) + + assert comm._supports_interface("camera", IConfig) + + +@pytest.mark.asyncio +async def test_execute(mocker): + comm = LocalComm("test") + + another_comm = LocalComm("camera") + another_comm.module = TestModule() + + mocker.patch.object(comm._network, "get_client", return_value=another_comm) + + assert await comm.execute("camera", "get_image_type", {"return": ImageType}) == ImageType.BIAS + comm._network.get_client.assert_called_once_with("camera") + + +@pytest.mark.asyncio +async def test_send_event(mocker): + comm = LocalComm("test") + + another_comm = LocalComm("camera") + another_comm.module = TestModule() + + mocker.patch.object(comm._network, "get_clients", return_value=[another_comm]) + mocker.patch.object(another_comm, "_send_event_to_module") + + await comm.send_event(Event()) + another_comm._send_event_to_module.assert_called_once() + diff --git a/tests/comm/local/test_localnetwork.py b/tests/comm/local/test_localnetwork.py index e69de29b..945e5e7e 100644 --- a/tests/comm/local/test_localnetwork.py +++ b/tests/comm/local/test_localnetwork.py @@ -0,0 +1,44 @@ +from pyobs.comm.local import LocalNetwork, LocalComm + + +def test_singleton(): + net1 = LocalNetwork() + net2 = LocalNetwork() + + assert net1 == net2 + + +def test_connect_client(): + net = LocalNetwork() + client = LocalComm("test") + + net.connect_client(client) + + assert client == net._clients["test"] + + +def test_get_client(): + net = LocalNetwork() + client = LocalComm("test") + + net._clients = {"test": client} + + assert client == net.get_client("test") + + +def test_get_clients(): + net = LocalNetwork() + client = LocalComm("test") + + net._clients = {"test": client} + + assert [client] == net.get_clients() + + +def test_get_client_names(): + net = LocalNetwork() + client = LocalComm("test") + + net._clients = {"test": client} + + assert ["test"] == net.get_client_names() From e8999fae4ab359d93fb66a51832e7da9f82932de Mon Sep 17 00:00:00 2001 From: GermanHydrogen Date: Tue, 31 Oct 2023 15:58:30 +0100 Subject: [PATCH 09/22] Added test basis to dotnet ometry --- tests/images/processors/__init__.py | 0 .../images/processors/astrometry/__init__.py | 0 .../processors/astrometry/test_dotnet.py | 240 ++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 tests/images/processors/__init__.py create mode 100644 tests/images/processors/astrometry/__init__.py create mode 100644 tests/images/processors/astrometry/test_dotnet.py diff --git a/tests/images/processors/__init__.py b/tests/images/processors/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/images/processors/astrometry/__init__.py b/tests/images/processors/astrometry/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/images/processors/astrometry/test_dotnet.py b/tests/images/processors/astrometry/test_dotnet.py new file mode 100644 index 00000000..c0bde274 --- /dev/null +++ b/tests/images/processors/astrometry/test_dotnet.py @@ -0,0 +1,240 @@ +import json + +import numpy as np +import pytest +from astropy.io.fits import Header +from astropy.table import QTable + +import pyobs.utils.exceptions as exc +from pyobs.images import Image +from pyobs.images.processors.astrometry import AstrometryDotNet + + +def test_init_default(): + url = "https://nova.astrometry.net" + + astrometry = AstrometryDotNet(url) + + assert astrometry.url == url + assert astrometry.source_count == 50 + assert astrometry.radius == 3.0 + assert astrometry.timeout == 10 + assert astrometry.exceptions is True + + +def test_init_w_values(): + url = "https://nova.astrometry.net" + source_count = 100 + radius = 10.0 + timeout = 60 + exceptions = False + + astrometry = AstrometryDotNet(url, source_count, radius, timeout, exceptions) + + assert astrometry.source_count == source_count + assert astrometry.radius == radius + assert astrometry.timeout == timeout + assert astrometry.exceptions == exceptions + + +def check_astrometry_header_exist(image, inverse=False) -> bool: + keywords = [ + "CTYPE1", + "CTYPE2", + "CRPIX1", + "CRPIX2", + "CRVAL1", + "CRVAL2", + "CD1_1", + "CD1_2", + "CD2_1", + "CD2_2", + ] + + keyword_in_hdr = [x in image.header for x in keywords] + + if inverse: + return not any(keyword_in_hdr) + else: + return all(keyword_in_hdr) + + +@pytest.mark.asyncio +async def test_call_n_catalog_n_exception(): + image = Image() + url = "https://nova.astrometry.net" + astrometry = AstrometryDotNet(url, exceptions=False) + + result_img = await astrometry(image) + assert check_astrometry_header_exist(result_img, True) + + +@pytest.mark.asyncio +async def test_call_n_catalog_w_exception(): + image = Image() + url = "https://nova.astrometry.net" + astrometry = AstrometryDotNet(url, exceptions=True) + + with pytest.raises(exc.ImageError): + await astrometry(image) + + +def mock_catalog(size: int): + x = [0.0] * size + [np.nan, np.nan] + y = [0.0] * size + [np.nan, np.nan] + flux = [1.0] * size + [np.nan, np.nan] + peak = [1.0] * size + [np.nan, np.nan] + + cat = QTable([x, y, flux, peak], names=('x', 'y', 'flux', 'peak')) + + return cat + + +@pytest.mark.asyncio +async def test_call_small_catalog_n_exception(): + image = Image() + image.catalog = mock_catalog(2) + url = "https://nova.astrometry.net" + astrometry = AstrometryDotNet(url, exceptions=False) + + result_img = await astrometry(image) + assert check_astrometry_header_exist(result_img, True) + assert result_img.header["WCSERR"] == 1 + + +@pytest.mark.asyncio +async def test_call_small_catalog_w_exception(): + image = Image() + image.catalog = mock_catalog(2) + url = "https://nova.astrometry.net" + astrometry = AstrometryDotNet(url, exceptions=True) + + with pytest.raises(exc.ImageError): + await astrometry(image) + + +@pytest.mark.asyncio +async def test_call_cdelt_n_exception(): + image = Image() + image.catalog = mock_catalog(5) + url = "https://nova.astrometry.net" + astrometry = AstrometryDotNet(url, exceptions=False) + + result_img = await astrometry(image) + assert check_astrometry_header_exist(result_img, True) + assert result_img.header["WCSERR"] == 1 + + +@pytest.mark.asyncio +async def test_call_cdelt_w_exception(): + image = Image() + image.catalog = mock_catalog(5) + url = "https://nova.astrometry.net" + astrometry = AstrometryDotNet(url, exceptions=True) + + with pytest.raises(exc.ImageError): + await astrometry(image) + + +def mock_header(): + header = Header() + header["CDELT1"] = 1.0 + header["CDELT2"] = 1.0 + + header["TEL-RA"] = 0.0 + header["TEL-DEC"] = 0.0 + + header["NAXIS1"] = 1.0 + header["NAXIS2"] = 1.0 + + header["CRPIX1"] = 1.0 + header["CRPIX2"] = 1.0 + + for keyword in ["PC1_1", "PC1_2", "PC2_1", "PC2_2"]: + header[keyword] = 0.0 + + return header + + +class MockResponse: + """https://stackoverflow.com/questions/57699218/how-can-i-mock-out-responses-made-by-aiohttp-clientsession""" + def __init__(self, text, status): + self._text = text + self.status = status + + async def text(self): + return self._text + + async def json(self): + return json.loads(self._text) + + async def __aexit__(self, exc_type, exc, tb): + pass + + async def __aenter__(self): + return self + + +@pytest.mark.asyncio +async def test_call_post_error_n_exception(mocker): + image = Image() + image.header = mock_header() + image.catalog = mock_catalog(5) + url = "https://nova.astrometry.net" + astrometry = AstrometryDotNet(url, exceptions=False) + + resp = MockResponse(json.dumps({}), 404) + mocker.patch("aiohttp.ClientSession.post", return_value=resp) + + result_image = await astrometry(image) + assert result_image.header["WCSERR"] == 1 + + +@pytest.mark.asyncio +async def test_call_post_error_w_exception(mocker): + image = Image() + image.header = mock_header() + image.catalog = mock_catalog(5) + url = "https://nova.astrometry.net" + astrometry = AstrometryDotNet(url, exceptions=True) + + resp = MockResponse(json.dumps({}), 404) + mocker.patch("aiohttp.ClientSession.post", return_value=resp) + + with pytest.raises(exc.ImageError): + await astrometry(image) + +@pytest.fixture() +def mock_response_data(): + keywords = [ + "CRPIX1", + "CRPIX2", + "CRVAL1", + "CRVAL2", + "CD1_1", + "CD1_2", + "CD2_1", + "CD2_2", + ] + + data = {x: 0.0 for x in keywords} + data["CTYPE1"] = "RA" + data["CTYPE2"] = "DEC" + + return data + + +@pytest.mark.asyncio +async def test_call_success(mocker, mock_response_data): + image = Image() + image.header = mock_header() + image.catalog = mock_catalog(5) + url = "https://nova.astrometry.net" + astrometry = AstrometryDotNet(url, exceptions=False) + + resp = MockResponse(json.dumps(mock_response_data), 200) + mocker.patch("aiohttp.ClientSession.post", return_value=resp) + + result_image = await astrometry(image) + assert result_image.header["WCSERR"] == 0 + assert all(result_image.header[x] == mock_response_data[x] for x in mock_response_data.keys()) \ No newline at end of file From 6e4c844ce4136ce70b4767fe47846655ff63c9b8 Mon Sep 17 00:00:00 2001 From: GermanHydrogen Date: Tue, 31 Oct 2023 15:59:19 +0100 Subject: [PATCH 10/22] Added pytest-mock and pytest-cov --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 1558c95d..d59f909e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,8 @@ types-tornado = "^5.1.1" mypy = "^1.5.1" pytest = "^7.4.2" pytest-asyncio = "^0.21.1" +pytest-cov = "^4.1.0" +pytest-mock = "^3.12.0" black = "^23.9.1" pre-commit = "^3.4.0" From e50c814c6cc9218230709076943d511d15c01a20 Mon Sep 17 00:00:00 2001 From: GermanHydrogen Date: Tue, 31 Oct 2023 17:21:26 +0100 Subject: [PATCH 11/22] Added more detailed tests for the web request --- .../processors/astrometry/test_dotnet.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/images/processors/astrometry/test_dotnet.py b/tests/images/processors/astrometry/test_dotnet.py index c0bde274..66410631 100644 --- a/tests/images/processors/astrometry/test_dotnet.py +++ b/tests/images/processors/astrometry/test_dotnet.py @@ -184,11 +184,29 @@ async def test_call_post_error_n_exception(mocker): astrometry = AstrometryDotNet(url, exceptions=False) resp = MockResponse(json.dumps({}), 404) - mocker.patch("aiohttp.ClientSession.post", return_value=resp) + mock = mocker.patch("aiohttp.ClientSession.post", return_value=resp) result_image = await astrometry(image) assert result_image.header["WCSERR"] == 1 + assert mock.call_args_list[0].args[0] == url + assert mock.call_args_list[0].kwargs["timeout"] == 10 + + data = mock.call_args_list[0].kwargs["json"] + + assert data == { + "ra": image.header["TEL-RA"], + "dec": image.header["TEL-DEC"], + "scale_low": 3600 * 0.9, + "scale_high": 3600 * 1.1, + "radius": 3.0, + "nx": image.header["NAXIS1"], + "ny": image.header["NAXIS2"], + "x": [0.0, 0.0, 0.0, 0.0, 0.0], + "y": [0.0, 0.0, 0.0, 0.0, 0.0], + "flux": [1.0, 1.0, 1.0, 1.0, 1.0], + } + @pytest.mark.asyncio async def test_call_post_error_w_exception(mocker): From b076a73b7b2a7c2177d27fdc1feb4e6aa97f46f0 Mon Sep 17 00:00:00 2001 From: GermanHydrogen Date: Tue, 31 Oct 2023 19:12:46 +0100 Subject: [PATCH 12/22] RefactoreAstrometryDotNet --- pyobs/images/processors/astrometry/dotnet.py | 295 +++++++++++-------- 1 file changed, 172 insertions(+), 123 deletions(-) diff --git a/pyobs/images/processors/astrometry/dotnet.py b/pyobs/images/processors/astrometry/dotnet.py index 37b4994d..253d16d7 100644 --- a/pyobs/images/processors/astrometry/dotnet.py +++ b/pyobs/images/processors/astrometry/dotnet.py @@ -1,7 +1,9 @@ import logging -from typing import Any +from typing import Any, Dict import aiohttp +import pandas as pd from astropy.coordinates import SkyCoord +from astropy.io.fits import Header from astropy.wcs import WCS import astropy.units as u @@ -9,7 +11,6 @@ import pyobs.utils.exceptions as exc from .astrometry import Astrometry - log = logging.getLogger(__name__) @@ -19,13 +20,13 @@ class AstrometryDotNet(Astrometry): __module__ = "pyobs.images.processors.astrometry" def __init__( - self, - url: str, - source_count: int = 50, - radius: float = 3.0, - timeout: int = 10, - exceptions: bool = True, - **kwargs: Any, + self, + url: str, + source_count: int = 50, + radius: float = 3.0, + timeout: int = 10, + exceptions: bool = True, + **kwargs: Any, ): """Init new astronomy.net processor. @@ -45,65 +46,58 @@ def __init__( self.timeout = timeout self.exceptions = exceptions - async def __call__(self, image: Image) -> Image: - """Find astrometric solution on given image. + @staticmethod + def _get_catalog(image: Image) -> pd.DataFrame: + if image.catalog is None: + log.warning("No catalog found in image.") + raise exc.ImageError("No catalog found in image.") - Writes WCSERR=1 into FITS header on failure. + return image.catalog[["x", "y", "flux", "peak"]].to_pandas() - Args: - image: Image to analyse. - """ + @staticmethod + def _filter_catalog(catalogue: pd.DataFrame) -> pd.DataFrame: + catalogue = catalogue.dropna() + catalogue = catalogue[catalogue["peak"] < 60000] - # copy image - img = image.copy() + return catalogue - # get catalog - if img.catalog is None: - log.warning("No catalog found in image.") - if self.exceptions: - raise exc.ImageError("No catalog found in image.") - return image - cat = img.catalog[["x", "y", "flux", "peak"]].to_pandas().dropna() - - # nothing? - if cat is None or len(cat) < 3: - log.warning("Not enough sources for astrometry.") - img.header["WCSERR"] = 1 - if self.exceptions: - raise exc.ImageError("Not enough sources for astrometry.") - return img - - # sort it, remove saturated stars and take N brightest sources - cat = cat.sort_values("flux", ascending=False) - cat = cat[cat["peak"] < 60000] - cat = cat[: self.source_count] - - # no CDELT1? - if "CDELT1" not in img.header: - log.warning("No CDELT1 found in header.") - img.header["WCSERR"] = 1 - if self.exceptions: - raise exc.ImageError("No CDELT1 found in header.") - return img - - # build request data - scale = abs(img.header["CDELT1"]) * 3600 + @staticmethod + def _validate_catalogue(catalogue: pd.DataFrame): + if catalogue is None or len(catalogue) < 3: + raise exc.ImageError("Not enough sources for astrometry.") + + def _select_brightest_stars(self, catalogue: pd.DataFrame) -> pd.DataFrame: + catalogue = catalogue.sort_values("flux", ascending=False) + catalogue = catalogue[: self.source_count] + + return catalogue + + @staticmethod + def _validate_header(header: Header): + if "CDELT1" not in header: + raise exc.ImageError("No CDELT1 found in header.") + + def _build_request_data(self, image: Image, catalogue: pd.DataFrame): + scale = abs(image.header["CDELT1"]) * 3600 data = { - "ra": img.header["TEL-RA"], - "dec": img.header["TEL-DEC"], + "ra": image.header["TEL-RA"], + "dec": image.header["TEL-DEC"], "scale_low": scale * 0.9, "scale_high": scale * 1.1, "radius": self.radius, - "nx": img.header["NAXIS1"], - "ny": img.header["NAXIS2"], - "x": cat["x"].tolist(), - "y": cat["y"].tolist(), - "flux": cat["flux"].tolist(), + "nx": image.header["NAXIS1"], + "ny": image.header["NAXIS2"], + "x": catalogue["x"].tolist(), + "y": catalogue["y"].tolist(), + "flux": catalogue["flux"].tolist(), } - # log it + return data + + @staticmethod + def _log_catalogue_data(image: Image, data: Dict[str, Any]): ra_dec = SkyCoord(ra=data["ra"] * u.deg, dec=data["dec"] * u.deg, frame="icrs") - cx, cy = img.header["CRPIX1"], img.header["CRPIX2"] + cx, cy = image.header["CRPIX1"], image.header["CRPIX2"] log.info( "Found original RA=%s (%.4f), Dec=%s (%.4f) at pixel %.2f,%.2f.", ra_dec.ra.to_string(sep=":", unit=u.hour, pad=True), @@ -114,80 +108,135 @@ async def __call__(self, image: Image) -> Image: cy, ) - # send it + async def _send_request(self, data: Dict[str, Any]) -> (Dict[str, any], int): async with aiohttp.ClientSession() as session: async with session.post(self.url, json=data, timeout=self.timeout) as response: status_code = response.status json = await response.json() + return json, status_code + + @staticmethod + def _generate_request_error_msg(json: Dict[str, Any]) -> str: + if "error" not in json: + return "Could not connect to astrometry service." + + if json["error"] == "Could not find WCS file.": + return "Could not determine WCS." + + return f"Received error from astrometry service: {json['error']}" + + def _handle_request_error(self, json: Dict[str, Any]): + error_msg = self._generate_request_error_msg(json) + raise exc.ImageError(error_msg) + + @staticmethod + def _copy_response_into_header(header: Header, json: Dict[str, Any]): + header_keywords_to_update = [ + "CTYPE1", + "CTYPE2", + "CRPIX1", + "CRPIX2", + "CRVAL1", + "CRVAL2", + "CD1_1", + "CD1_2", + "CD2_1", + "CD2_2", + ] + + for keyword in header_keywords_to_update: + header[keyword] = json[keyword] + + @staticmethod + def _delete_old_wcs_data(header: Header): + """ + astrometry.net gives a CD matrix, so we have to delete the PC matrix and the CDELT* parameters + """ + for keyword in ["PC1_1", "PC1_2", "PC2_1", "PC2_2", "CDELT1", "CDELT2"]: + del header[keyword] + + @staticmethod + def _add_plate_solution_to_catalogue(catalogue: pd.DataFrame, image_wcs: WCS): + ras, decs = image_wcs.all_pix2world(catalogue["x"], catalogue["y"], 1) + + catalogue["ra"] = ras + catalogue["dec"] = decs + + @staticmethod + def _log_request_result(image: Image, image_wcs: WCS, data: Dict[str, Any]): + + cx, cy = image.header["CRPIX1"], image.header["CRPIX2"] + final_ra, final_dec = image_wcs.all_pix2world(cx, cy, 0) + ra_dec = SkyCoord(ra=final_ra * u.deg, dec=final_dec * u.deg, frame="icrs") + + # log it + log.info( + "Found final RA=%s (%.4f), Dec=%s (%.4f) at pixel %.2f,%.2f.", + ra_dec.ra.to_string(sep=":", unit=u.hour, pad=True), + data["ra"], + ra_dec.dec.to_string(sep=":", unit=u.deg, pad=True), + data["dec"], + cx, + cy, + ) + + async def _process(self, image: Image) -> Image: + img = image.copy() + + catalogue = self._get_catalog(image) + filtered_catalogue = self._filter_catalog(catalogue) + + self._validate_catalogue(filtered_catalogue) + + reduced_catalogue = self._select_brightest_stars(filtered_catalogue) + + self._validate_header(img.header) + + data = self._build_request_data(img, reduced_catalogue) + + self._log_catalogue_data(image, data) + + json, status_code = await self._send_request(data) + # success? if status_code != 200 or "error" in json: - # set error - img.header["WCSERR"] = 1 - msg = "Could not connect to astrometry service." - if "error" in json: - # "Could not find WCS file." is just an info, which means that WCS was not successful - if json["error"] == "Could not find WCS file.": - msg = "Could not determine WCS." - else: - msg = f"Received error from astrometry service: {json['error']}" - - # raise or warn? - if self.exceptions: - raise exc.ImageError(msg) - else: - log.warning(msg) - - else: - # copy keywords - hdr = json - header_keywords_to_update = [ - "CTYPE1", - "CTYPE2", - "CRPIX1", - "CRPIX2", - "CRVAL1", - "CRVAL2", - "CD1_1", - "CD1_2", - "CD2_1", - "CD2_2", - ] - for keyword in header_keywords_to_update: - img.header[keyword] = hdr[keyword] - - # astrometry.net gives a CD matrix, so we have to delete the PC matrix and the CDELT* parameters - for keyword in ["PC1_1", "PC1_2", "PC2_1", "PC2_2", "CDELT1", "CDELT2"]: - del img.header[keyword] - - # calculate world coordinates for all sources in catalog - image_wcs = WCS(img.header) - ras, decs = image_wcs.all_pix2world(img.catalog["x"], img.catalog["y"], 1) - - # set them - img.catalog["ra"] = ras - img.catalog["dec"] = decs - - # RA/Dec at center pos - final_ra, final_dec = image_wcs.all_pix2world(cx, cy, 0) - ra_dec = SkyCoord(ra=final_ra * u.deg, dec=final_dec * u.deg, frame="icrs") - - # log it - log.info( - "Found final RA=%s (%.4f), Dec=%s (%.4f) at pixel %.2f,%.2f.", - ra_dec.ra.to_string(sep=":", unit=u.hour, pad=True), - data["ra"], - ra_dec.dec.to_string(sep=":", unit=u.deg, pad=True), - data["dec"], - cx, - cy, - ) - - # success - img.header["WCSERR"] = 0 - - # finished + self._handle_request_error(json) + + self._copy_response_into_header(img.header, json) + self._delete_old_wcs_data(img.header) + + image_wcs = WCS(img.header) + self._add_plate_solution_to_catalogue(img.catalog, image_wcs) + self._log_request_result(image, image_wcs, data) + + # huge success + img.header["WCSERR"] = 0 return img + def _handle_error(self, image: Image, error: exc.ImageError): + if self.exceptions: + raise error + + image.header["WCSERR"] = 1 + + log.warning(error.message) + + return image + + async def __call__(self, image: Image) -> Image: + """Find astrometric solution on given image. + + Writes WCSERR=1 into FITS header on failure. + + Args: + image: Image to analyse. + """ + + try: + return await self._process(image) + except exc.ImageError as e: + return self._handle_error(image, e) + __all__ = ["AstrometryDotNet"] From f8e829d81f42e2897cdaea1f9deca30422f35d2a Mon Sep 17 00:00:00 2001 From: GermanHydrogen Date: Wed, 1 Nov 2023 09:34:16 +0100 Subject: [PATCH 13/22] Added finer test cases --- pyobs/images/processors/astrometry/dotnet.py | 7 +- .../processors/astrometry/test_dotnet.py | 70 +++++++++++++++++-- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/pyobs/images/processors/astrometry/dotnet.py b/pyobs/images/processors/astrometry/dotnet.py index 253d16d7..ce5827f7 100644 --- a/pyobs/images/processors/astrometry/dotnet.py +++ b/pyobs/images/processors/astrometry/dotnet.py @@ -49,17 +49,16 @@ def __init__( @staticmethod def _get_catalog(image: Image) -> pd.DataFrame: if image.catalog is None: - log.warning("No catalog found in image.") raise exc.ImageError("No catalog found in image.") return image.catalog[["x", "y", "flux", "peak"]].to_pandas() @staticmethod def _filter_catalog(catalogue: pd.DataFrame) -> pd.DataFrame: - catalogue = catalogue.dropna() - catalogue = catalogue[catalogue["peak"] < 60000] + res_catalogue = catalogue.dropna(how="any") + res_catalogue = res_catalogue[res_catalogue["peak"] < 60000] - return catalogue + return res_catalogue @staticmethod def _validate_catalogue(catalogue: pd.DataFrame): diff --git a/tests/images/processors/astrometry/test_dotnet.py b/tests/images/processors/astrometry/test_dotnet.py index 66410631..b663eede 100644 --- a/tests/images/processors/astrometry/test_dotnet.py +++ b/tests/images/processors/astrometry/test_dotnet.py @@ -1,9 +1,11 @@ import json +import logging import numpy as np import pytest from astropy.io.fits import Header from astropy.table import QTable +from astropy.wcs import WCS import pyobs.utils.exceptions as exc from pyobs.images import Image @@ -113,6 +115,16 @@ async def test_call_small_catalog_w_exception(): await astrometry(image) +def test_filter_catalogue(): + catalogue = mock_catalog(2) + pandas_catalogue = catalogue.to_pandas() + pandas_catalogue.iloc[0]["peak"] = 60001 + filtered_catalogue = AstrometryDotNet._filter_catalog(pandas_catalogue) + + assert True not in filtered_catalogue.isna() + assert len(filtered_catalogue[filtered_catalogue["peak"] >= 6000]) == 0 + + @pytest.mark.asyncio async def test_call_cdelt_n_exception(): image = Image() @@ -136,6 +148,7 @@ async def test_call_cdelt_w_exception(): await astrometry(image) +@pytest.fixture() def mock_header(): header = Header() header["CDELT1"] = 1.0 @@ -156,6 +169,21 @@ def mock_header(): return header +@pytest.mark.asyncio +async def test_log_catalogue_data(caplog, mock_header): + data = {"ra": 0.0, "dec": 0.0} + image = Image(header=mock_header) + + url = "https://nova.astrometry.net" + astrometry = AstrometryDotNet(url) + + with caplog.at_level(logging.INFO): + astrometry._log_catalogue_data(image, data) + + assert caplog.records[-1].message == "Found original RA=00:00:00 (0.0000), Dec=00:00:00 (0.0000) at pixel 1.00,1.00." + assert caplog.records[-1].levelname == "INFO" + + class MockResponse: """https://stackoverflow.com/questions/57699218/how-can-i-mock-out-responses-made-by-aiohttp-clientsession""" def __init__(self, text, status): @@ -176,9 +204,9 @@ async def __aenter__(self): @pytest.mark.asyncio -async def test_call_post_error_n_exception(mocker): +async def test_call_post_error_n_exception(mocker, mock_header): image = Image() - image.header = mock_header() + image.header = mock_header image.catalog = mock_catalog(5) url = "https://nova.astrometry.net" astrometry = AstrometryDotNet(url, exceptions=False) @@ -209,9 +237,9 @@ async def test_call_post_error_n_exception(mocker): @pytest.mark.asyncio -async def test_call_post_error_w_exception(mocker): +async def test_call_post_error_w_exception(mocker, mock_header): image = Image() - image.header = mock_header() + image.header = mock_header image.catalog = mock_catalog(5) url = "https://nova.astrometry.net" astrometry = AstrometryDotNet(url, exceptions=True) @@ -222,6 +250,7 @@ async def test_call_post_error_w_exception(mocker): with pytest.raises(exc.ImageError): await astrometry(image) + @pytest.fixture() def mock_response_data(): keywords = [ @@ -243,9 +272,9 @@ def mock_response_data(): @pytest.mark.asyncio -async def test_call_success(mocker, mock_response_data): +async def test_call_success(mocker, mock_response_data, mock_header): image = Image() - image.header = mock_header() + image.header = mock_header image.catalog = mock_catalog(5) url = "https://nova.astrometry.net" astrometry = AstrometryDotNet(url, exceptions=False) @@ -255,4 +284,31 @@ async def test_call_success(mocker, mock_response_data): result_image = await astrometry(image) assert result_image.header["WCSERR"] == 0 - assert all(result_image.header[x] == mock_response_data[x] for x in mock_response_data.keys()) \ No newline at end of file + assert all(result_image.header[x] == mock_response_data[x] for x in mock_response_data.keys()) + + +@pytest.mark.asyncio +async def test_log_request_result(caplog, mock_header): + data = {"ra": 0.0, "dec": 0.0} + image = Image(header=mock_header) + + url = "https://nova.astrometry.net" + astrometry = AstrometryDotNet(url) + + with caplog.at_level(logging.INFO): + astrometry._log_request_result(image, WCS(), data) + + assert caplog.records[-1].message == "Found final RA=00:08:00 (0.0000), Dec=02:00:00 (0.0000) at pixel 1.00,1.00." + assert caplog.records[-1].levelname == "INFO" + + +@pytest.mark.asyncio +async def test_generate_request_error_msg(): + data = {} + assert AstrometryDotNet._generate_request_error_msg(data) == "Could not connect to astrometry service." + + data = {"error": "Could not find WCS file."} + assert AstrometryDotNet._generate_request_error_msg(data) == "Could not determine WCS." + + data = {"error": "Test"} + assert AstrometryDotNet._generate_request_error_msg(data) == "Received error from astrometry service: Test" From cbb62372ee13bbb73c8df0caaa59fa327a123271 Mon Sep 17 00:00:00 2001 From: GermanHydrogen Date: Wed, 1 Nov 2023 09:48:46 +0100 Subject: [PATCH 14/22] Changed catalogue to catalog --- pyobs/images/processors/astrometry/dotnet.py | 52 +++++++++---------- .../processors/astrometry/test_dotnet.py | 18 +++---- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/pyobs/images/processors/astrometry/dotnet.py b/pyobs/images/processors/astrometry/dotnet.py index ce5827f7..5ee4c746 100644 --- a/pyobs/images/processors/astrometry/dotnet.py +++ b/pyobs/images/processors/astrometry/dotnet.py @@ -54,29 +54,29 @@ def _get_catalog(image: Image) -> pd.DataFrame: return image.catalog[["x", "y", "flux", "peak"]].to_pandas() @staticmethod - def _filter_catalog(catalogue: pd.DataFrame) -> pd.DataFrame: - res_catalogue = catalogue.dropna(how="any") - res_catalogue = res_catalogue[res_catalogue["peak"] < 60000] + def _filter_catalog(catalog: pd.DataFrame) -> pd.DataFrame: + res_catalog = catalog.dropna(how="any") + res_catalog = res_catalog[res_catalog["peak"] < 60000] - return res_catalogue + return res_catalog @staticmethod - def _validate_catalogue(catalogue: pd.DataFrame): - if catalogue is None or len(catalogue) < 3: + def _validate_catalog(catalog: pd.DataFrame): + if catalog is None or len(catalog) < 3: raise exc.ImageError("Not enough sources for astrometry.") - def _select_brightest_stars(self, catalogue: pd.DataFrame) -> pd.DataFrame: - catalogue = catalogue.sort_values("flux", ascending=False) - catalogue = catalogue[: self.source_count] + def _select_brightest_stars(self, catalog: pd.DataFrame) -> pd.DataFrame: + catalog = catalog.sort_values("flux", ascending=False) + catalog = catalog[: self.source_count] - return catalogue + return catalog @staticmethod def _validate_header(header: Header): if "CDELT1" not in header: raise exc.ImageError("No CDELT1 found in header.") - def _build_request_data(self, image: Image, catalogue: pd.DataFrame): + def _build_request_data(self, image: Image, catalog: pd.DataFrame): scale = abs(image.header["CDELT1"]) * 3600 data = { "ra": image.header["TEL-RA"], @@ -86,15 +86,15 @@ def _build_request_data(self, image: Image, catalogue: pd.DataFrame): "radius": self.radius, "nx": image.header["NAXIS1"], "ny": image.header["NAXIS2"], - "x": catalogue["x"].tolist(), - "y": catalogue["y"].tolist(), - "flux": catalogue["flux"].tolist(), + "x": catalog["x"].tolist(), + "y": catalog["y"].tolist(), + "flux": catalog["flux"].tolist(), } return data @staticmethod - def _log_catalogue_data(image: Image, data: Dict[str, Any]): + def _log_catalog_data(image: Image, data: Dict[str, Any]): ra_dec = SkyCoord(ra=data["ra"] * u.deg, dec=data["dec"] * u.deg, frame="icrs") cx, cy = image.header["CRPIX1"], image.header["CRPIX2"] log.info( @@ -156,11 +156,11 @@ def _delete_old_wcs_data(header: Header): del header[keyword] @staticmethod - def _add_plate_solution_to_catalogue(catalogue: pd.DataFrame, image_wcs: WCS): - ras, decs = image_wcs.all_pix2world(catalogue["x"], catalogue["y"], 1) + def _add_plate_solution_to_catalog(catalog: pd.DataFrame, image_wcs: WCS): + ras, decs = image_wcs.all_pix2world(catalog["x"], catalog["y"], 1) - catalogue["ra"] = ras - catalogue["dec"] = decs + catalog["ra"] = ras + catalog["dec"] = decs @staticmethod def _log_request_result(image: Image, image_wcs: WCS, data: Dict[str, Any]): @@ -183,18 +183,18 @@ def _log_request_result(image: Image, image_wcs: WCS, data: Dict[str, Any]): async def _process(self, image: Image) -> Image: img = image.copy() - catalogue = self._get_catalog(image) - filtered_catalogue = self._filter_catalog(catalogue) + catalog = self._get_catalog(image) + filtered_catalog = self._filter_catalog(catalog) - self._validate_catalogue(filtered_catalogue) + self._validate_catalog(filtered_catalog) - reduced_catalogue = self._select_brightest_stars(filtered_catalogue) + reduced_catalog = self._select_brightest_stars(filtered_catalog) self._validate_header(img.header) - data = self._build_request_data(img, reduced_catalogue) + data = self._build_request_data(img, reduced_catalog) - self._log_catalogue_data(image, data) + self._log_catalog_data(image, data) json, status_code = await self._send_request(data) @@ -206,7 +206,7 @@ async def _process(self, image: Image) -> Image: self._delete_old_wcs_data(img.header) image_wcs = WCS(img.header) - self._add_plate_solution_to_catalogue(img.catalog, image_wcs) + self._add_plate_solution_to_catalog(img.catalog, image_wcs) self._log_request_result(image, image_wcs, data) # huge success diff --git a/tests/images/processors/astrometry/test_dotnet.py b/tests/images/processors/astrometry/test_dotnet.py index b663eede..77b4860f 100644 --- a/tests/images/processors/astrometry/test_dotnet.py +++ b/tests/images/processors/astrometry/test_dotnet.py @@ -115,14 +115,14 @@ async def test_call_small_catalog_w_exception(): await astrometry(image) -def test_filter_catalogue(): - catalogue = mock_catalog(2) - pandas_catalogue = catalogue.to_pandas() - pandas_catalogue.iloc[0]["peak"] = 60001 - filtered_catalogue = AstrometryDotNet._filter_catalog(pandas_catalogue) +def test_filter_catalog(): + catalog = mock_catalog(2) + pandas_catalog = catalog.to_pandas() + pandas_catalog.iloc[0]["peak"] = 60001 + filtered_catalog = AstrometryDotNet._filter_catalog(pandas_catalog) - assert True not in filtered_catalogue.isna() - assert len(filtered_catalogue[filtered_catalogue["peak"] >= 6000]) == 0 + assert True not in filtered_catalog.isna() + assert len(filtered_catalog[filtered_catalog["peak"] >= 6000]) == 0 @pytest.mark.asyncio @@ -170,7 +170,7 @@ def mock_header(): @pytest.mark.asyncio -async def test_log_catalogue_data(caplog, mock_header): +async def test_log_catalog_data(caplog, mock_header): data = {"ra": 0.0, "dec": 0.0} image = Image(header=mock_header) @@ -178,7 +178,7 @@ async def test_log_catalogue_data(caplog, mock_header): astrometry = AstrometryDotNet(url) with caplog.at_level(logging.INFO): - astrometry._log_catalogue_data(image, data) + astrometry._log_catalog_data(image, data) assert caplog.records[-1].message == "Found original RA=00:00:00 (0.0000), Dec=00:00:00 (0.0000) at pixel 1.00,1.00." assert caplog.records[-1].levelname == "INFO" From 63c73373c51f8765e0efa8cb75efcc8c8247441b Mon Sep 17 00:00:00 2001 From: GermanHydrogen Date: Wed, 1 Nov 2023 10:06:12 +0100 Subject: [PATCH 15/22] Moved request building into seperate class --- .../astrometry/_dotnet_request_builder.py | 68 +++++++++++++++++++ pyobs/images/processors/astrometry/dotnet.py | 63 ++--------------- .../processors/astrometry/test_dotnet.py | 18 ++--- .../astrometry/test_dotnet_request_builder.py | 15 ++++ 4 files changed, 93 insertions(+), 71 deletions(-) create mode 100644 pyobs/images/processors/astrometry/_dotnet_request_builder.py create mode 100644 tests/images/processors/astrometry/test_dotnet_request_builder.py diff --git a/pyobs/images/processors/astrometry/_dotnet_request_builder.py b/pyobs/images/processors/astrometry/_dotnet_request_builder.py new file mode 100644 index 00000000..bc8c8436 --- /dev/null +++ b/pyobs/images/processors/astrometry/_dotnet_request_builder.py @@ -0,0 +1,68 @@ +from typing import Dict, Any + +import pandas as pd +from astropy.io.fits import Header + +from pyobs.images import Image +import pyobs.utils.exceptions as exc + + +class _DotNetRequestBuilder: + def __init__(self, source_count: int, radius: float): + self._source_count = source_count + self._radius = radius + + self._data = {} + self._catalog: pd.DataFrame = None + self._header: Header = None + + def add_catalog_from_image(self, image: Image): + if image.catalog is None: + raise exc.ImageError("No catalog found in image.") + + self._catalog = image.catalog[["x", "y", "flux", "peak"]].to_pandas() + + def add_header_from_image(self, image: Image): + self._header = image.header + + def _filter_catalog(self): + self._catalog = self._catalog.dropna(how="any") + self._catalog = self._catalog[self._catalog["peak"] < 60000] + + def _validate_catalog(self): + if self._catalog is None or len(self._catalog) < 3: + raise exc.ImageError("Not enough sources for astrometry.") + + def _select_brightest_stars(self): + self._catalog = self._catalog.sort_values("flux", ascending=False) + self._catalog = self._catalog[: self._source_count] + + def _validate_header(self): + if "CDELT1" not in self._header: + raise exc.ImageError("No CDELT1 found in header.") + + def _build_request_data(self): + scale = abs(self._header["CDELT1"]) * 3600 + self._data = { + "ra": self._header["TEL-RA"], + "dec": self._header["TEL-DEC"], + "scale_low": scale * 0.9, + "scale_high": scale * 1.1, + "radius": self._radius, + "nx": self._header["NAXIS1"], + "ny": self._header["NAXIS2"], + "x": self._catalog["x"].tolist(), + "y": self._catalog["y"].tolist(), + "flux": self._catalog["flux"].tolist(), + } + + def __call__(self) -> Dict[str, Any]: + self._validate_header() + + self._filter_catalog() + self._validate_catalog() + self._select_brightest_stars() + + self._build_request_data() + + return self._data diff --git a/pyobs/images/processors/astrometry/dotnet.py b/pyobs/images/processors/astrometry/dotnet.py index 5ee4c746..61d5a705 100644 --- a/pyobs/images/processors/astrometry/dotnet.py +++ b/pyobs/images/processors/astrometry/dotnet.py @@ -9,6 +9,7 @@ from pyobs.images import Image import pyobs.utils.exceptions as exc +from ._dotnet_request_builder import _DotNetRequestBuilder from .astrometry import Astrometry log = logging.getLogger(__name__) @@ -41,57 +42,11 @@ def __init__( # URL to web-service self.url = url - self.source_count = source_count - self.radius = radius + self.timeout = timeout self.exceptions = exceptions - @staticmethod - def _get_catalog(image: Image) -> pd.DataFrame: - if image.catalog is None: - raise exc.ImageError("No catalog found in image.") - - return image.catalog[["x", "y", "flux", "peak"]].to_pandas() - - @staticmethod - def _filter_catalog(catalog: pd.DataFrame) -> pd.DataFrame: - res_catalog = catalog.dropna(how="any") - res_catalog = res_catalog[res_catalog["peak"] < 60000] - - return res_catalog - - @staticmethod - def _validate_catalog(catalog: pd.DataFrame): - if catalog is None or len(catalog) < 3: - raise exc.ImageError("Not enough sources for astrometry.") - - def _select_brightest_stars(self, catalog: pd.DataFrame) -> pd.DataFrame: - catalog = catalog.sort_values("flux", ascending=False) - catalog = catalog[: self.source_count] - - return catalog - - @staticmethod - def _validate_header(header: Header): - if "CDELT1" not in header: - raise exc.ImageError("No CDELT1 found in header.") - - def _build_request_data(self, image: Image, catalog: pd.DataFrame): - scale = abs(image.header["CDELT1"]) * 3600 - data = { - "ra": image.header["TEL-RA"], - "dec": image.header["TEL-DEC"], - "scale_low": scale * 0.9, - "scale_high": scale * 1.1, - "radius": self.radius, - "nx": image.header["NAXIS1"], - "ny": image.header["NAXIS2"], - "x": catalog["x"].tolist(), - "y": catalog["y"].tolist(), - "flux": catalog["flux"].tolist(), - } - - return data + self._request_builder = _DotNetRequestBuilder(source_count, radius) @staticmethod def _log_catalog_data(image: Image, data: Dict[str, Any]): @@ -183,16 +138,10 @@ def _log_request_result(image: Image, image_wcs: WCS, data: Dict[str, Any]): async def _process(self, image: Image) -> Image: img = image.copy() - catalog = self._get_catalog(image) - filtered_catalog = self._filter_catalog(catalog) - - self._validate_catalog(filtered_catalog) - - reduced_catalog = self._select_brightest_stars(filtered_catalog) - - self._validate_header(img.header) + self._request_builder.add_catalog_from_image(img) + self._request_builder.add_header_from_image(img) - data = self._build_request_data(img, reduced_catalog) + data = self._request_builder() self._log_catalog_data(image, data) diff --git a/tests/images/processors/astrometry/test_dotnet.py b/tests/images/processors/astrometry/test_dotnet.py index 77b4860f..484d80a4 100644 --- a/tests/images/processors/astrometry/test_dotnet.py +++ b/tests/images/processors/astrometry/test_dotnet.py @@ -18,8 +18,8 @@ def test_init_default(): astrometry = AstrometryDotNet(url) assert astrometry.url == url - assert astrometry.source_count == 50 - assert astrometry.radius == 3.0 + assert astrometry._request_builder._source_count == 50 + assert astrometry._request_builder._radius == 3.0 assert astrometry.timeout == 10 assert astrometry.exceptions is True @@ -33,8 +33,8 @@ def test_init_w_values(): astrometry = AstrometryDotNet(url, source_count, radius, timeout, exceptions) - assert astrometry.source_count == source_count - assert astrometry.radius == radius + assert astrometry._request_builder._source_count == source_count + assert astrometry._request_builder._radius == radius assert astrometry.timeout == timeout assert astrometry.exceptions == exceptions @@ -115,16 +115,6 @@ async def test_call_small_catalog_w_exception(): await astrometry(image) -def test_filter_catalog(): - catalog = mock_catalog(2) - pandas_catalog = catalog.to_pandas() - pandas_catalog.iloc[0]["peak"] = 60001 - filtered_catalog = AstrometryDotNet._filter_catalog(pandas_catalog) - - assert True not in filtered_catalog.isna() - assert len(filtered_catalog[filtered_catalog["peak"] >= 6000]) == 0 - - @pytest.mark.asyncio async def test_call_cdelt_n_exception(): image = Image() diff --git a/tests/images/processors/astrometry/test_dotnet_request_builder.py b/tests/images/processors/astrometry/test_dotnet_request_builder.py new file mode 100644 index 00000000..149d9f51 --- /dev/null +++ b/tests/images/processors/astrometry/test_dotnet_request_builder.py @@ -0,0 +1,15 @@ +from pyobs.images.processors.astrometry._dotnet_request_builder import _DotNetRequestBuilder +from tests.images.processors.astrometry.test_dotnet import mock_catalog + + +def test_filter_catalog(): + request_builder = _DotNetRequestBuilder(50, 3.0) + + catalog = mock_catalog(2) + pandas_catalog = catalog.to_pandas() + pandas_catalog.iloc[0]["peak"] = 60001 + request_builder._catalog = pandas_catalog + request_builder._filter_catalog() + + assert True not in request_builder._catalog.isna() + assert len(request_builder._catalog[request_builder._catalog["peak"] >= 6000]) == 0 From fab04e023bd2e04b991afe3c23e42949d7365a92 Mon Sep 17 00:00:00 2001 From: GermanHydrogen Date: Wed, 1 Nov 2023 10:10:11 +0100 Subject: [PATCH 16/22] Fixed execution order --- pyobs/images/processors/astrometry/_dotnet_request_builder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyobs/images/processors/astrometry/_dotnet_request_builder.py b/pyobs/images/processors/astrometry/_dotnet_request_builder.py index bc8c8436..1f8329ac 100644 --- a/pyobs/images/processors/astrometry/_dotnet_request_builder.py +++ b/pyobs/images/processors/astrometry/_dotnet_request_builder.py @@ -57,12 +57,12 @@ def _build_request_data(self): } def __call__(self) -> Dict[str, Any]: - self._validate_header() - self._filter_catalog() self._validate_catalog() self._select_brightest_stars() + self._validate_header() + self._build_request_data() return self._data From 607d61a8e1b914c3c5ccd60aee66bf4ebb780198 Mon Sep 17 00:00:00 2001 From: GermanHydrogen Date: Wed, 1 Nov 2023 10:20:39 +0100 Subject: [PATCH 17/22] Fixed docstring --- pyobs/images/processors/astrometry/astrometry.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyobs/images/processors/astrometry/astrometry.py b/pyobs/images/processors/astrometry/astrometry.py index e2607d7d..05aa6347 100644 --- a/pyobs/images/processors/astrometry/astrometry.py +++ b/pyobs/images/processors/astrometry/astrometry.py @@ -11,7 +11,7 @@ class Astrometry(ImageProcessor, metaclass=ABCMeta): @abstractmethod async def __call__(self, image: Image) -> Image: - """Find astrometric solution on given image. + """Finds astrometric solution to a given image. Args: image: Image to analyse. @@ -19,7 +19,6 @@ async def __call__(self, image: Image) -> Image: Returns: Processed image. """ - ... __all__ = ["Astrometry"] From 08d5782ff119381a5fb8c8e9a7a7b38a1fb9037c Mon Sep 17 00:00:00 2001 From: GermanHydrogen Date: Wed, 1 Nov 2023 10:27:11 +0100 Subject: [PATCH 18/22] Fixed indentation --- pyobs/images/processors/astrometry/dotnet.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pyobs/images/processors/astrometry/dotnet.py b/pyobs/images/processors/astrometry/dotnet.py index 61d5a705..1f6c7708 100644 --- a/pyobs/images/processors/astrometry/dotnet.py +++ b/pyobs/images/processors/astrometry/dotnet.py @@ -21,13 +21,13 @@ class AstrometryDotNet(Astrometry): __module__ = "pyobs.images.processors.astrometry" def __init__( - self, - url: str, - source_count: int = 50, - radius: float = 3.0, - timeout: int = 10, - exceptions: bool = True, - **kwargs: Any, + self, + url: str, + source_count: int = 50, + radius: float = 3.0, + timeout: int = 10, + exceptions: bool = True, + **kwargs: Any, ): """Init new astronomy.net processor. From 6c909e7172e306f4128c2c3fff6759698c5f1cdb Mon Sep 17 00:00:00 2001 From: GermanHydrogen Date: Sun, 5 Nov 2023 08:31:44 +0100 Subject: [PATCH 19/22] Refactored variable names --- .../astrometry/_dotnet_request_builder.py | 6 +- pyobs/images/processors/astrometry/dotnet.py | 90 +++++++++---------- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/pyobs/images/processors/astrometry/_dotnet_request_builder.py b/pyobs/images/processors/astrometry/_dotnet_request_builder.py index 1f8329ac..ce08be26 100644 --- a/pyobs/images/processors/astrometry/_dotnet_request_builder.py +++ b/pyobs/images/processors/astrometry/_dotnet_request_builder.py @@ -12,7 +12,7 @@ def __init__(self, source_count: int, radius: float): self._source_count = source_count self._radius = radius - self._data = {} + self._request_data = {} self._catalog: pd.DataFrame = None self._header: Header = None @@ -43,7 +43,7 @@ def _validate_header(self): def _build_request_data(self): scale = abs(self._header["CDELT1"]) * 3600 - self._data = { + self._request_data = { "ra": self._header["TEL-RA"], "dec": self._header["TEL-DEC"], "scale_low": scale * 0.9, @@ -65,4 +65,4 @@ def __call__(self) -> Dict[str, Any]: self._build_request_data() - return self._data + return self._request_data diff --git a/pyobs/images/processors/astrometry/dotnet.py b/pyobs/images/processors/astrometry/dotnet.py index 1f6c7708..5d5bc1e5 100644 --- a/pyobs/images/processors/astrometry/dotnet.py +++ b/pyobs/images/processors/astrometry/dotnet.py @@ -49,43 +49,43 @@ def __init__( self._request_builder = _DotNetRequestBuilder(source_count, radius) @staticmethod - def _log_catalog_data(image: Image, data: Dict[str, Any]): - ra_dec = SkyCoord(ra=data["ra"] * u.deg, dec=data["dec"] * u.deg, frame="icrs") - cx, cy = image.header["CRPIX1"], image.header["CRPIX2"] + def _log_catalog_data(image: Image, request_data: Dict[str, Any]): + ra_dec = SkyCoord(ra=request_data["ra"] * u.deg, dec=request_data["dec"] * u.deg, frame="icrs") + center_x, center_y = image.header["CRPIX1"], image.header["CRPIX2"] log.info( "Found original RA=%s (%.4f), Dec=%s (%.4f) at pixel %.2f,%.2f.", ra_dec.ra.to_string(sep=":", unit=u.hour, pad=True), - data["ra"], + request_data["ra"], ra_dec.dec.to_string(sep=":", unit=u.deg, pad=True), - data["dec"], - cx, - cy, + request_data["dec"], + center_x, + center_y, ) - async def _send_request(self, data: Dict[str, Any]) -> (Dict[str, any], int): + async def _send_request(self, request_data: Dict[str, Any]) -> (Dict[str, any], int): async with aiohttp.ClientSession() as session: - async with session.post(self.url, json=data, timeout=self.timeout) as response: - status_code = response.status - json = await response.json() + async with session.post(self.url, json=request_data, timeout=self.timeout) as response: + response_status_code = response.status + response_data = await response.json() - return json, status_code + return response_data, response_status_code @staticmethod - def _generate_request_error_msg(json: Dict[str, Any]) -> str: - if "error" not in json: + def _generate_request_error_msg(response_data: Dict[str, Any]) -> str: + if "error" not in response_data: return "Could not connect to astrometry service." - if json["error"] == "Could not find WCS file.": + if response_data["error"] == "Could not find WCS file.": return "Could not determine WCS." - return f"Received error from astrometry service: {json['error']}" + return f"Received error from astrometry service: {response_data['error']}" - def _handle_request_error(self, json: Dict[str, Any]): - error_msg = self._generate_request_error_msg(json) + def _handle_request_error(self, response_data: Dict[str, Any]): + error_msg = self._generate_request_error_msg(response_data) raise exc.ImageError(error_msg) @staticmethod - def _copy_response_into_header(header: Header, json: Dict[str, Any]): + def _copy_response_into_header(header: Header, response_data: Dict[str, Any]): header_keywords_to_update = [ "CTYPE1", "CTYPE2", @@ -100,7 +100,7 @@ def _copy_response_into_header(header: Header, json: Dict[str, Any]): ] for keyword in header_keywords_to_update: - header[keyword] = json[keyword] + header[keyword] = response_data[keyword] @staticmethod def _delete_old_wcs_data(header: Header): @@ -118,49 +118,49 @@ def _add_plate_solution_to_catalog(catalog: pd.DataFrame, image_wcs: WCS): catalog["dec"] = decs @staticmethod - def _log_request_result(image: Image, image_wcs: WCS, data: Dict[str, Any]): + def _log_request_result(image: Image, image_wcs: WCS, response_data: Dict[str, Any]): - cx, cy = image.header["CRPIX1"], image.header["CRPIX2"] - final_ra, final_dec = image_wcs.all_pix2world(cx, cy, 0) - ra_dec = SkyCoord(ra=final_ra * u.deg, dec=final_dec * u.deg, frame="icrs") + center_x, center_y = image.header["CRPIX1"], image.header["CRPIX2"] + final_ra, final_dec = image_wcs.all_pix2world(center_x, center_y, 0) + final_ra_dec = SkyCoord(ra=final_ra * u.deg, dec=final_dec * u.deg, frame="icrs") # log it log.info( "Found final RA=%s (%.4f), Dec=%s (%.4f) at pixel %.2f,%.2f.", - ra_dec.ra.to_string(sep=":", unit=u.hour, pad=True), - data["ra"], - ra_dec.dec.to_string(sep=":", unit=u.deg, pad=True), - data["dec"], - cx, - cy, + final_ra_dec.ra.to_string(sep=":", unit=u.hour, pad=True), + response_data["ra"], + final_ra_dec.dec.to_string(sep=":", unit=u.deg, pad=True), + response_data["dec"], + center_x, + center_y, ) async def _process(self, image: Image) -> Image: - img = image.copy() + result_image = image.copy() - self._request_builder.add_catalog_from_image(img) - self._request_builder.add_header_from_image(img) + self._request_builder.add_catalog_from_image(result_image) + self._request_builder.add_header_from_image(result_image) - data = self._request_builder() + request_data = self._request_builder() - self._log_catalog_data(image, data) + self._log_catalog_data(image, request_data) - json, status_code = await self._send_request(data) + response_data, response_status_code = await self._send_request(request_data) # success? - if status_code != 200 or "error" in json: - self._handle_request_error(json) + if response_status_code != 200 or "error" in response_data: + self._handle_request_error(response_data) - self._copy_response_into_header(img.header, json) - self._delete_old_wcs_data(img.header) + self._copy_response_into_header(result_image.header, response_data) + self._delete_old_wcs_data(result_image.header) - image_wcs = WCS(img.header) - self._add_plate_solution_to_catalog(img.catalog, image_wcs) - self._log_request_result(image, image_wcs, data) + image_wcs = WCS(result_image.header) + self._add_plate_solution_to_catalog(result_image.catalog, image_wcs) + self._log_request_result(result_image, image_wcs, request_data) # huge success - img.header["WCSERR"] = 0 - return img + result_image.header["WCSERR"] = 0 + return result_image def _handle_error(self, image: Image, error: exc.ImageError): if self.exceptions: From 20c8447877398422d4adfeea2bcdabf20dbb8de4 Mon Sep 17 00:00:00 2001 From: GermanHydrogen Date: Sun, 5 Nov 2023 10:17:54 +0100 Subject: [PATCH 20/22] Split dotnet process inequest builder, re logger and saver --- .../processors/astrometry/_dotnet_request.py | 49 +++++++ .../astrometry/_dotnet_request_builder.py | 5 +- .../astrometry/_dotnet_request_logger.py | 44 ++++++ .../astrometry/_dotnet_response_saver.py | 66 +++++++++ pyobs/images/processors/astrometry/dotnet.py | 128 ++---------------- .../images/processors/astrometry/conftest.py | 23 ++++ .../processors/astrometry/test_dotnet.py | 63 --------- .../astrometry/test_dotnet_request.py | 16 +++ .../astrometry/test_dotnet_request_logger.py | 36 +++++ 9 files changed, 250 insertions(+), 180 deletions(-) create mode 100644 pyobs/images/processors/astrometry/_dotnet_request.py create mode 100644 pyobs/images/processors/astrometry/_dotnet_request_logger.py create mode 100644 pyobs/images/processors/astrometry/_dotnet_response_saver.py create mode 100644 tests/images/processors/astrometry/conftest.py create mode 100644 tests/images/processors/astrometry/test_dotnet_request.py create mode 100644 tests/images/processors/astrometry/test_dotnet_request_logger.py diff --git a/pyobs/images/processors/astrometry/_dotnet_request.py b/pyobs/images/processors/astrometry/_dotnet_request.py new file mode 100644 index 00000000..b1ccdbd6 --- /dev/null +++ b/pyobs/images/processors/astrometry/_dotnet_request.py @@ -0,0 +1,49 @@ +from typing import Dict, Optional + +import aiohttp + +import pyobs.utils.exceptions as exc + + +class _DotNetRequest: + def __init__(self, request_data: Dict[str, any]): + self._request_data = request_data + + self._response_data: Optional[Dict[str, any]] = None + self._status_code: Optional[int] = None + + async def _send_request(self, url: str, timeout: int): + async with aiohttp.ClientSession() as session: + async with session.post(url, json=self._request_data, timeout=timeout) as response: + self._status_code = response.status + self._response_data = await response.json() + + def _generate_request_error_msg(self) -> str: + if "error" not in self._response_data: + return "Could not connect to astrometry service." + + if self._response_data["error"] == "Could not find WCS file.": + return "Could not determine WCS." + + return f"Received error from astrometry service: {self._response_data['error']}" + + def _handle_request_error(self): + error_msg = self._generate_request_error_msg() + raise exc.ImageError(error_msg) + + def _is_request_successful(self): + return self._status_code != 200 or "error" in self._response_data + + async def send(self, url: str, timeout: int): + await self._send_request(url, timeout) + + if self._is_request_successful(): + self._handle_request_error() + + @property + def request_data(self) -> Dict[str, any]: + return self._request_data + + @property + def response_data(self) -> Optional[Dict[str, any]]: + return self._response_data diff --git a/pyobs/images/processors/astrometry/_dotnet_request_builder.py b/pyobs/images/processors/astrometry/_dotnet_request_builder.py index ce08be26..a36c8321 100644 --- a/pyobs/images/processors/astrometry/_dotnet_request_builder.py +++ b/pyobs/images/processors/astrometry/_dotnet_request_builder.py @@ -5,6 +5,7 @@ from pyobs.images import Image import pyobs.utils.exceptions as exc +from pyobs.images.processors.astrometry._dotnet_request import _DotNetRequest class _DotNetRequestBuilder: @@ -56,7 +57,7 @@ def _build_request_data(self): "flux": self._catalog["flux"].tolist(), } - def __call__(self) -> Dict[str, Any]: + def __call__(self) -> _DotNetRequest: self._filter_catalog() self._validate_catalog() self._select_brightest_stars() @@ -65,4 +66,4 @@ def __call__(self) -> Dict[str, Any]: self._build_request_data() - return self._request_data + return _DotNetRequest(self._request_data) diff --git a/pyobs/images/processors/astrometry/_dotnet_request_logger.py b/pyobs/images/processors/astrometry/_dotnet_request_logger.py new file mode 100644 index 00000000..6ddf0274 --- /dev/null +++ b/pyobs/images/processors/astrometry/_dotnet_request_logger.py @@ -0,0 +1,44 @@ +import logging +from typing import Dict, Any + +from astropy.coordinates import SkyCoord +import astropy.units as u +from astropy.wcs import WCS + +from pyobs.images import Image + + +class _RequestLogger: + def __init__(self, logger: logging.Logger, image: Image, request_data: Dict[str, Any]): + self._logger = logger + self._image = image + self._request_data = request_data + + def log_request_data(self): + ra_dec = SkyCoord(ra=self._request_data["ra"] * u.deg, dec=self._request_data["dec"] * u.deg, frame="icrs") + center_x, center_y = self._image.header["CRPIX1"], self._image.header["CRPIX2"] + + self._logger.info( + "Found original RA=%s (%.4f), Dec=%s (%.4f) at pixel %.2f,%.2f.", + ra_dec.ra.to_string(sep=":", unit=u.hour, pad=True), + self._request_data["ra"], + ra_dec.dec.to_string(sep=":", unit=u.deg, pad=True), + self._request_data["dec"], + center_x, + center_y, + ) + + def log_request_result(self, image_wcs: WCS): + center_x, center_y = self._image.header["CRPIX1"], self._image.header["CRPIX2"] + final_ra, final_dec = image_wcs.all_pix2world(center_x, center_y, 0) + final_ra_dec = SkyCoord(ra=final_ra * u.deg, dec=final_dec * u.deg, frame="icrs") + + self._logger.info( + "Found final RA=%s (%.4f), Dec=%s (%.4f) at pixel %.2f,%.2f.", + final_ra_dec.ra.to_string(sep=":", unit=u.hour, pad=True), + self._request_data["ra"], + final_ra_dec.dec.to_string(sep=":", unit=u.deg, pad=True), + self._request_data["dec"], + center_x, + center_y, + ) \ No newline at end of file diff --git a/pyobs/images/processors/astrometry/_dotnet_response_saver.py b/pyobs/images/processors/astrometry/_dotnet_response_saver.py new file mode 100644 index 00000000..19084e1f --- /dev/null +++ b/pyobs/images/processors/astrometry/_dotnet_response_saver.py @@ -0,0 +1,66 @@ +from copy import copy +from typing import Dict, Any, Optional + +from astropy.wcs import WCS + +from pyobs.images import Image + + +class _ResponseImageWriter: + def __init__(self, response_data: Dict[str, Any], image: Image): + self._response_data = response_data + self._image = copy(image) + + self._image_wcs: Optional[WCS] = None + + @property + def response_data(self): + return self._response_data + + @property + def image(self): + return self._image + + @property + def image_wcs(self): + return self._image_wcs + + def _write_response_into_header(self): + header_keywords_to_update = [ + "CTYPE1", "CTYPE2", + "CRPIX1", "CRPIX2", + "CRVAL1", "CRVAL2", + "CD1_1", "CD1_2", + "CD2_1", "CD2_2", + ] + + for keyword in header_keywords_to_update: + self._image.header[keyword] = self._response_data[keyword] + + def _delete_old_wcs_data(self): + """ + astrometry.net gives a CD matrix, so we have to delete the PC matrix and the CDELT* parameters + """ + for keyword in ["PC1_1", "PC1_2", "PC2_1", "PC2_2", "CDELT1", "CDELT2"]: + del self._image.header[keyword] + + def _generate_image_wcs(self): + self._image_wcs = WCS(self._image.header) + + def _add_plate_solution_to_catalog(self): + ras, decs = self._image_wcs.all_pix2world(self._image.catalog["x"], self._image.catalog["y"], 1) + + self._image.catalog["ra"] = ras + self._image.catalog["dec"] = decs + + def _add_wcs_err_success(self): + self._image.header["WCSERR"] = 0 + + def __call__(self, *args, **kwargs) -> Image: + self._write_response_into_header() + self._delete_old_wcs_data() + self._generate_image_wcs() + self._add_plate_solution_to_catalog() + self._add_wcs_err_success() + + return self._image diff --git a/pyobs/images/processors/astrometry/dotnet.py b/pyobs/images/processors/astrometry/dotnet.py index 5d5bc1e5..0d7df503 100644 --- a/pyobs/images/processors/astrometry/dotnet.py +++ b/pyobs/images/processors/astrometry/dotnet.py @@ -1,15 +1,11 @@ import logging -from typing import Any, Dict -import aiohttp -import pandas as pd -from astropy.coordinates import SkyCoord -from astropy.io.fits import Header -from astropy.wcs import WCS -import astropy.units as u +from typing import Any -from pyobs.images import Image import pyobs.utils.exceptions as exc +from pyobs.images import Image from ._dotnet_request_builder import _DotNetRequestBuilder +from ._dotnet_request_logger import _RequestLogger +from ._dotnet_response_saver import _ResponseImageWriter from .astrometry import Astrometry log = logging.getLogger(__name__) @@ -40,7 +36,6 @@ def __init__( """ Astrometry.__init__(self, **kwargs) - # URL to web-service self.url = url self.timeout = timeout @@ -48,118 +43,21 @@ def __init__( self._request_builder = _DotNetRequestBuilder(source_count, radius) - @staticmethod - def _log_catalog_data(image: Image, request_data: Dict[str, Any]): - ra_dec = SkyCoord(ra=request_data["ra"] * u.deg, dec=request_data["dec"] * u.deg, frame="icrs") - center_x, center_y = image.header["CRPIX1"], image.header["CRPIX2"] - log.info( - "Found original RA=%s (%.4f), Dec=%s (%.4f) at pixel %.2f,%.2f.", - ra_dec.ra.to_string(sep=":", unit=u.hour, pad=True), - request_data["ra"], - ra_dec.dec.to_string(sep=":", unit=u.deg, pad=True), - request_data["dec"], - center_x, - center_y, - ) - - async def _send_request(self, request_data: Dict[str, Any]) -> (Dict[str, any], int): - async with aiohttp.ClientSession() as session: - async with session.post(self.url, json=request_data, timeout=self.timeout) as response: - response_status_code = response.status - response_data = await response.json() - - return response_data, response_status_code - - @staticmethod - def _generate_request_error_msg(response_data: Dict[str, Any]) -> str: - if "error" not in response_data: - return "Could not connect to astrometry service." - - if response_data["error"] == "Could not find WCS file.": - return "Could not determine WCS." - - return f"Received error from astrometry service: {response_data['error']}" - - def _handle_request_error(self, response_data: Dict[str, Any]): - error_msg = self._generate_request_error_msg(response_data) - raise exc.ImageError(error_msg) - - @staticmethod - def _copy_response_into_header(header: Header, response_data: Dict[str, Any]): - header_keywords_to_update = [ - "CTYPE1", - "CTYPE2", - "CRPIX1", - "CRPIX2", - "CRVAL1", - "CRVAL2", - "CD1_1", - "CD1_2", - "CD2_1", - "CD2_2", - ] - - for keyword in header_keywords_to_update: - header[keyword] = response_data[keyword] - - @staticmethod - def _delete_old_wcs_data(header: Header): - """ - astrometry.net gives a CD matrix, so we have to delete the PC matrix and the CDELT* parameters - """ - for keyword in ["PC1_1", "PC1_2", "PC2_1", "PC2_2", "CDELT1", "CDELT2"]: - del header[keyword] - - @staticmethod - def _add_plate_solution_to_catalog(catalog: pd.DataFrame, image_wcs: WCS): - ras, decs = image_wcs.all_pix2world(catalog["x"], catalog["y"], 1) - - catalog["ra"] = ras - catalog["dec"] = decs - - @staticmethod - def _log_request_result(image: Image, image_wcs: WCS, response_data: Dict[str, Any]): - - center_x, center_y = image.header["CRPIX1"], image.header["CRPIX2"] - final_ra, final_dec = image_wcs.all_pix2world(center_x, center_y, 0) - final_ra_dec = SkyCoord(ra=final_ra * u.deg, dec=final_dec * u.deg, frame="icrs") - - # log it - log.info( - "Found final RA=%s (%.4f), Dec=%s (%.4f) at pixel %.2f,%.2f.", - final_ra_dec.ra.to_string(sep=":", unit=u.hour, pad=True), - response_data["ra"], - final_ra_dec.dec.to_string(sep=":", unit=u.deg, pad=True), - response_data["dec"], - center_x, - center_y, - ) - async def _process(self, image: Image) -> Image: - result_image = image.copy() - - self._request_builder.add_catalog_from_image(result_image) - self._request_builder.add_header_from_image(result_image) - - request_data = self._request_builder() - - self._log_catalog_data(image, request_data) + self._request_builder.add_catalog_from_image(image) + self._request_builder.add_header_from_image(image) + request = self._request_builder() - response_data, response_status_code = await self._send_request(request_data) + logger = _RequestLogger(log, image, request.request_data) + logger.log_request_data() - # success? - if response_status_code != 200 or "error" in response_data: - self._handle_request_error(response_data) + await request.send(self.url, self.timeout) - self._copy_response_into_header(result_image.header, response_data) - self._delete_old_wcs_data(result_image.header) + response_writer = _ResponseImageWriter(request.response_data, image) + result_image = response_writer() - image_wcs = WCS(result_image.header) - self._add_plate_solution_to_catalog(result_image.catalog, image_wcs) - self._log_request_result(result_image, image_wcs, request_data) + logger.log_request_result(response_writer.image_wcs) - # huge success - result_image.header["WCSERR"] = 0 return result_image def _handle_error(self, image: Image, error: exc.ImageError): diff --git a/tests/images/processors/astrometry/conftest.py b/tests/images/processors/astrometry/conftest.py new file mode 100644 index 00000000..1ddff524 --- /dev/null +++ b/tests/images/processors/astrometry/conftest.py @@ -0,0 +1,23 @@ +import pytest +from astropy.io.fits import Header + + +@pytest.fixture(scope="module") +def mock_header(): + header = Header() + header["CDELT1"] = 1.0 + header["CDELT2"] = 1.0 + + header["TEL-RA"] = 0.0 + header["TEL-DEC"] = 0.0 + + header["NAXIS1"] = 1.0 + header["NAXIS2"] = 1.0 + + header["CRPIX1"] = 1.0 + header["CRPIX2"] = 1.0 + + for keyword in ["PC1_1", "PC1_2", "PC2_1", "PC2_2"]: + header[keyword] = 0.0 + + return header \ No newline at end of file diff --git a/tests/images/processors/astrometry/test_dotnet.py b/tests/images/processors/astrometry/test_dotnet.py index 484d80a4..c9cc0b0b 100644 --- a/tests/images/processors/astrometry/test_dotnet.py +++ b/tests/images/processors/astrometry/test_dotnet.py @@ -138,42 +138,6 @@ async def test_call_cdelt_w_exception(): await astrometry(image) -@pytest.fixture() -def mock_header(): - header = Header() - header["CDELT1"] = 1.0 - header["CDELT2"] = 1.0 - - header["TEL-RA"] = 0.0 - header["TEL-DEC"] = 0.0 - - header["NAXIS1"] = 1.0 - header["NAXIS2"] = 1.0 - - header["CRPIX1"] = 1.0 - header["CRPIX2"] = 1.0 - - for keyword in ["PC1_1", "PC1_2", "PC2_1", "PC2_2"]: - header[keyword] = 0.0 - - return header - - -@pytest.mark.asyncio -async def test_log_catalog_data(caplog, mock_header): - data = {"ra": 0.0, "dec": 0.0} - image = Image(header=mock_header) - - url = "https://nova.astrometry.net" - astrometry = AstrometryDotNet(url) - - with caplog.at_level(logging.INFO): - astrometry._log_catalog_data(image, data) - - assert caplog.records[-1].message == "Found original RA=00:00:00 (0.0000), Dec=00:00:00 (0.0000) at pixel 1.00,1.00." - assert caplog.records[-1].levelname == "INFO" - - class MockResponse: """https://stackoverflow.com/questions/57699218/how-can-i-mock-out-responses-made-by-aiohttp-clientsession""" def __init__(self, text, status): @@ -275,30 +239,3 @@ async def test_call_success(mocker, mock_response_data, mock_header): result_image = await astrometry(image) assert result_image.header["WCSERR"] == 0 assert all(result_image.header[x] == mock_response_data[x] for x in mock_response_data.keys()) - - -@pytest.mark.asyncio -async def test_log_request_result(caplog, mock_header): - data = {"ra": 0.0, "dec": 0.0} - image = Image(header=mock_header) - - url = "https://nova.astrometry.net" - astrometry = AstrometryDotNet(url) - - with caplog.at_level(logging.INFO): - astrometry._log_request_result(image, WCS(), data) - - assert caplog.records[-1].message == "Found final RA=00:08:00 (0.0000), Dec=02:00:00 (0.0000) at pixel 1.00,1.00." - assert caplog.records[-1].levelname == "INFO" - - -@pytest.mark.asyncio -async def test_generate_request_error_msg(): - data = {} - assert AstrometryDotNet._generate_request_error_msg(data) == "Could not connect to astrometry service." - - data = {"error": "Could not find WCS file."} - assert AstrometryDotNet._generate_request_error_msg(data) == "Could not determine WCS." - - data = {"error": "Test"} - assert AstrometryDotNet._generate_request_error_msg(data) == "Received error from astrometry service: Test" diff --git a/tests/images/processors/astrometry/test_dotnet_request.py b/tests/images/processors/astrometry/test_dotnet_request.py new file mode 100644 index 00000000..2f129576 --- /dev/null +++ b/tests/images/processors/astrometry/test_dotnet_request.py @@ -0,0 +1,16 @@ +import pytest + +from pyobs.images.processors.astrometry._dotnet_request import _DotNetRequest + + +@pytest.mark.asyncio +async def test_generate_request_error_msg(): + request = _DotNetRequest({}) + request._response_data = {} + assert request._generate_request_error_msg() == "Could not connect to astrometry service." + + request._response_data = {"error": "Could not find WCS file."} + assert request._generate_request_error_msg() == "Could not determine WCS." + + request._response_data = {"error": "Test"} + assert request._generate_request_error_msg() == "Received error from astrometry service: Test" \ No newline at end of file diff --git a/tests/images/processors/astrometry/test_dotnet_request_logger.py b/tests/images/processors/astrometry/test_dotnet_request_logger.py new file mode 100644 index 00000000..7aa9c1be --- /dev/null +++ b/tests/images/processors/astrometry/test_dotnet_request_logger.py @@ -0,0 +1,36 @@ +import logging + +from astropy.wcs import WCS + +from pyobs.images import Image +from pyobs.images.processors.astrometry._dotnet_request_logger import _RequestLogger + + +def test_log_catalog_data(caplog, mock_header): + log = logging.getLogger(__name__) + + data = {"ra": 0.0, "dec": 0.0} + image = Image(header=mock_header) + + logger = _RequestLogger(log, image, data) + + with caplog.at_level(logging.INFO): + logger.log_request_data() + + assert caplog.records[-1].message == "Found original RA=00:00:00 (0.0000), Dec=00:00:00 (0.0000) at pixel 1.00,1.00." + assert caplog.records[-1].levelname == "INFO" + + +def test_log_request_result(caplog, mock_header): + log = logging.getLogger(__name__) + + data = {"ra": 0.0, "dec": 0.0} + image = Image(header=mock_header) + + logger = _RequestLogger(log, image, data) + + with caplog.at_level(logging.INFO): + logger.log_request_result(WCS()) + + assert caplog.records[-1].message == "Found final RA=00:08:00 (0.0000), Dec=02:00:00 (0.0000) at pixel 1.00,1.00." + assert caplog.records[-1].levelname == "INFO" From 5bdc9c616ee1c35ef23860ccf74bff3682c813d6 Mon Sep 17 00:00:00 2001 From: Tim-Oliver Husser Date: Tue, 7 Nov 2023 21:56:47 +0100 Subject: [PATCH 21/22] error to warning --- pyobs/images/processors/misc/calibration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyobs/images/processors/misc/calibration.py b/pyobs/images/processors/misc/calibration.py index 62af6d25..00cdbf10 100644 --- a/pyobs/images/processors/misc/calibration.py +++ b/pyobs/images/processors/misc/calibration.py @@ -84,7 +84,7 @@ async def __call__(self, image: Image) -> Image: else await self._find_master(image, ImageType.SKYFLAT, max_days=self._max_days_flat) ) except ValueError as e: - log.error("Could not find calibration frames: " + str(e)) + log.warning("Could not find calibration frames: " + str(e)) return image # trim image From 0d3d08f5576014b12d479d2aa09018b4b78501de Mon Sep 17 00:00:00 2001 From: Tim-Oliver Husser Date: Tue, 7 Nov 2023 21:59:24 +0100 Subject: [PATCH 22/22] v1.6.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d59f909e..93a81c45 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "pyobs-core" packages = [{ include = "pyobs" }] -version = "1.5.8" +version = "1.6.0" description = "robotic telescope software" authors = ["Tim-Oliver Husser "] license = "MIT"