From 0104c4e8f310d437c1559595aaf57bf07a69eea4 Mon Sep 17 00:00:00 2001 From: jadon <16850875+wolffshots@users.noreply.github.com> Date: Wed, 24 Apr 2024 19:34:35 +0200 Subject: [PATCH 01/20] feat: start of new sensor for libraries counting --- custom_components/audiobookshelf/__init__.py | 13 +++++ custom_components/audiobookshelf/const.py | 2 +- .../audiobookshelf/manifest.json | 2 +- custom_components/audiobookshelf/sensor.py | 50 +++++++++++++++++-- pyproject.toml | 2 +- tests/test_sensor.py | 12 ++--- 6 files changed, 67 insertions(+), 14 deletions(-) diff --git a/custom_components/audiobookshelf/__init__.py b/custom_components/audiobookshelf/__init__.py index ca31d1a..50d3313 100644 --- a/custom_components/audiobookshelf/__init__.py +++ b/custom_components/audiobookshelf/__init__.py @@ -1,6 +1,7 @@ """Init for audiobookshelf integration""" import asyncio +import json import logging from homeassistant.config_entries import ConfigEntry @@ -86,6 +87,18 @@ async def _async_update_data(self) -> dict[str, None]: update["sessions"] = "TimeoutError: Request timed out." except HTTPError as http_error: update["sessions"] = f"HTTPError: Generic HTTP Error happened {http_error}" + try: + library_stats_update = await self.api.api_wrapper( + method="get", + url=self.api.get_host() + "/api/libraries", + ) + update["libraries"] = json.loads(library_stats_update) + except ConnectionError: + update["libraries"] = "ConnectionError: Unable to connect." + except (TimeoutError, Timeout): + update["libraries"] = "TimeoutError: Request timed out." + except HTTPError as http_error: + update["libraries"] = f"HTTPError: Generic HTTP Error happened {http_error}" return update async def async_setup(hass: HomeAssistant, config: Config) -> bool: diff --git a/custom_components/audiobookshelf/const.py b/custom_components/audiobookshelf/const.py index 4db0e41..c63a69d 100644 --- a/custom_components/audiobookshelf/const.py +++ b/custom_components/audiobookshelf/const.py @@ -6,7 +6,7 @@ NAME = "Audiobookshelf" DOMAIN = "audiobookshelf" DOMAIN_DATA = f"{DOMAIN}_data" -VERSION = "v0.0.6" +VERSION = "v0.1.0" ATTRIBUTION = "Server by https://www.audiobookshelf.org/" ISSUE_URL = "https://github.com/wolffshots/hass-audiobookshelf/issues" diff --git a/custom_components/audiobookshelf/manifest.json b/custom_components/audiobookshelf/manifest.json index 9febe94..344f5ad 100644 --- a/custom_components/audiobookshelf/manifest.json +++ b/custom_components/audiobookshelf/manifest.json @@ -8,5 +8,5 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/wolffshots/hass-audiobookshelf/issues", "requirements": [], - "version": "v0.0.6" + "version": "v0.1.0" } diff --git a/custom_components/audiobookshelf/sensor.py b/custom_components/audiobookshelf/sensor.py index fccf7b7..c227b8d 100644 --- a/custom_components/audiobookshelf/sensor.py +++ b/custom_components/audiobookshelf/sensor.py @@ -18,16 +18,16 @@ async def async_setup_entry( ) -> None: """Setup sensor platform.""" coordinator = hass.data[DOMAIN][entry.entry_id] - async_add_devices([AudiobookshelfSensor(coordinator, entry)]) + async_add_devices([AudiobookshelfSessionsSensor(coordinator, entry)]) -class AudiobookshelfSensor(AudiobookshelfEntity): - """audiobookshelf Sensor class.""" +class AudiobookshelfSessionsSensor(AudiobookshelfEntity): + """audiobookshelf Sessions Sensor class.""" @property def name(self) -> str: """Return the name of the sensor.""" - return f"{DOMAIN}_sessions" + return f"{DOMAIN} Sessions" @property def state(self) -> int | None: @@ -36,7 +36,7 @@ def state(self) -> int | None: coordinator_get = self.coordinator.data.get( "sessions", "", - ) # need to work out how to add functionality to the coordinator to fetch /api/users + ) _LOGGER.debug("""sensor coordinator got: %s""", coordinator_get) if isinstance(coordinator_get, int): @@ -59,3 +59,43 @@ def icon(self) -> str: def device_class(self) -> str: """Return device class of the sensor.""" return "audiobookshelf__custom_device_class" + +class AudiobookshelfNumberOfLibrariesSensor(AudiobookshelfEntity): + """audiobookshelf Number of Libraries Sensor class.""" + + @property + def name(self) -> str: + """Return the name of the sensor.""" + return f"{DOMAIN} Number of Libraries" + + @property + def state(self) -> int | None: + """Return the state of the sensor.""" + try: + coordinator_get: dict | str = self.coordinator.data.get( + "libraries", + "", + ) + _LOGGER.debug("""sensor coordinator got: %s""", coordinator_get) + + if not isinstance(coordinator_get, str): + # count and return int + return len(coordinator_get["libraries"]) + + return None + + except AttributeError: + _LOGGER.debug( + "sensor: AttributeError caught while accessing coordinator data.", + ) + return None + + @property + def icon(self) -> str: + """Return the icon of the sensor.""" + return "mdi:format-quote-close" + + @property + def device_class(self) -> str: + """Return device class of the sensor.""" + return "audiobookshelf__custom_device_class" diff --git a/pyproject.toml b/pyproject.toml index cdfb3be..c7ef7db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ max-complexity = 15 [tool.poetry] name = "audiobookshelf" -version = "v0.0.6" +version = "v0.1.0" description = "Audiobookshelf HA custom component" authors = ["wolffshots <16850875+wolffshots@users.noreply.github.com>"] readme = "README.md" diff --git a/tests/test_sensor.py b/tests/test_sensor.py index da5cddc..6e83898 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -10,7 +10,7 @@ DOMAIN, ) from custom_components.audiobookshelf.sensor import ( - AudiobookshelfSensor, + AudiobookshelfSessionsSensor, async_setup_entry, ) @@ -53,7 +53,7 @@ async def test_sensor_init_entry( """Test the initialisation.""" entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="sensors") m_add_entities = Mock() - m_device = AudiobookshelfSensor( + m_device = AudiobookshelfSessionsSensor( coordinator=mock_coordinator, config_entry=entry, ) @@ -65,7 +65,7 @@ async def test_sensor_init_entry( await async_setup_entry(hass, entry, m_add_entities) assert isinstance( hass.data[DOMAIN]["sensors"]["audiobookshelf_sessions"], - AudiobookshelfSensor, + AudiobookshelfSessionsSensor, ) m_add_entities.assert_called_once() @@ -73,7 +73,7 @@ async def test_sensor_init_entry( async def test_sensor_properties(mock_coordinator: Mock) -> None: """Test that the sensor returns the correct properties""" config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="sensors") - sensor = AudiobookshelfSensor( + sensor = AudiobookshelfSessionsSensor( coordinator=mock_coordinator, config_entry=config_entry, ) @@ -86,7 +86,7 @@ async def test_sensor_properties(mock_coordinator: Mock) -> None: async def test_sensor_unknown(mock_coordinator_unknown: Mock) -> None: """Test that the sensor returns the correct properties""" config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="sensors") - sensor = AudiobookshelfSensor( + sensor = AudiobookshelfSessionsSensor( coordinator=mock_coordinator_unknown, config_entry=config_entry, ) @@ -100,7 +100,7 @@ async def test_sensor_error( """Test for exception handling on exception on coordinator""" caplog.clear() config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="sensors") - sensor = AudiobookshelfSensor( + sensor = AudiobookshelfSessionsSensor( coordinator=mock_coordinator_error, config_entry=config_entry, ) From 1cf592bc93e5ce0969bd1e75bae372474e581db9 Mon Sep 17 00:00:00 2001 From: jadon <16850875+wolffshots@users.noreply.github.com> Date: Wed, 24 Apr 2024 19:42:58 +0200 Subject: [PATCH 02/20] fix: don't try re-parse dict --- custom_components/audiobookshelf/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/custom_components/audiobookshelf/__init__.py b/custom_components/audiobookshelf/__init__.py index 50d3313..6b23898 100644 --- a/custom_components/audiobookshelf/__init__.py +++ b/custom_components/audiobookshelf/__init__.py @@ -1,7 +1,6 @@ """Init for audiobookshelf integration""" import asyncio -import json import logging from homeassistant.config_entries import ConfigEntry @@ -92,7 +91,7 @@ async def _async_update_data(self) -> dict[str, None]: method="get", url=self.api.get_host() + "/api/libraries", ) - update["libraries"] = json.loads(library_stats_update) + update["libraries"] = library_stats_update except ConnectionError: update["libraries"] = "ConnectionError: Unable to connect." except (TimeoutError, Timeout): From fedf190973a8172fdd05be9001fb9202ddb14d10 Mon Sep 17 00:00:00 2001 From: jadon <16850875+wolffshots@users.noreply.github.com> Date: Wed, 24 Apr 2024 19:51:10 +0200 Subject: [PATCH 03/20] fix: add library sensor to setup and update readmes --- README.md | 11 ++++++----- custom_components/audiobookshelf/sensor.py | 5 ++++- info.md | 12 +++++------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 9ed367a..7b0ecf1 100644 --- a/README.md +++ b/README.md @@ -11,12 +11,13 @@ [![Project Maintenance][maintenance-shield]][user_profile] [![BuyMeCoffee][buymecoffeebadge]][buymecoffee] -**This component will set up the following platforms.** +**This component will set up the following sensors.** -| Entity | Description | -| ------------------------------ | ------------------------------------ | -| `binary_sensor`:`connectivity` | Show whether the server is connected | -| `sensor`:`open_sessions` | Show number of open audio sessions | +| Entity | Type | Description | +| --------------- | ---------------- | ------------------------------------ | +| `connectivity` | `binary_sensor` | Show whether the server is connected | +| `open_sessions` | `sensor` | Show number of open audio sessions | +| `libraries` | `sensor` | Number of libraries on the server | ## Installation diff --git a/custom_components/audiobookshelf/sensor.py b/custom_components/audiobookshelf/sensor.py index c227b8d..9ec2348 100644 --- a/custom_components/audiobookshelf/sensor.py +++ b/custom_components/audiobookshelf/sensor.py @@ -18,7 +18,10 @@ async def async_setup_entry( ) -> None: """Setup sensor platform.""" coordinator = hass.data[DOMAIN][entry.entry_id] - async_add_devices([AudiobookshelfSessionsSensor(coordinator, entry)]) + async_add_devices([ + AudiobookshelfSessionsSensor(coordinator, entry), + AudiobookshelfNumberOfLibrariesSensor(coordinator, entry), + ]) class AudiobookshelfSessionsSensor(AudiobookshelfEntity): diff --git a/info.md b/info.md index be3e4ed..7f55596 100644 --- a/info.md +++ b/info.md @@ -2,19 +2,17 @@ [![GitHub Activity][commits-shield]][commits] [![License][license-shield]](LICENSE) -[![pre-commit][pre-commit-shield]][pre-commit] -[![Black][black-shield]][black] - [![hacs][hacsbadge]][hacs] [![Project Maintenance][maintenance-shield]][user_profile] [![BuyMeCoffee][buymecoffeebadge]][buymecoffee] **This component will set up the following platforms.** -| Entity | Description | -| ------------------------------ | ------------------------------------ | -| `binary_sensor`:`connectivity` | Show whether the server is connected | -| `sensor`:`open_sessions` | Show number of open audio sessions | +| Entity | Type | Description | +| --------------- | ---------------- | ------------------------------------ | +| `connectivity` | `binary_sensor` | Show whether the server is connected | +| `open_sessions` | `sensor` | Show number of open audio sessions | +| `libraries` | `sensor` | Number of libraries on the server | {% if not installed %} From c6c4473ec7c2a51cc8824e4b2741c785c589153d Mon Sep 17 00:00:00 2001 From: jadon <16850875+wolffshots@users.noreply.github.com> Date: Wed, 24 Apr 2024 20:03:29 +0200 Subject: [PATCH 04/20] feat: try move unique ids into the sensors --- README.md | 2 +- custom_components/audiobookshelf/binary_sensor.py | 7 ++++++- custom_components/audiobookshelf/entity.py | 5 ----- custom_components/audiobookshelf/sensor.py | 10 ++++++++++ info.md | 2 +- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 7b0ecf1..d284347 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ | Entity | Type | Description | | --------------- | ---------------- | ------------------------------------ | | `connectivity` | `binary_sensor` | Show whether the server is connected | -| `open_sessions` | `sensor` | Show number of open audio sessions | +| `sessions` | `sensor` | Show number of open audio sessions | | `libraries` | `sensor` | Number of libraries on the server | ## Installation diff --git a/custom_components/audiobookshelf/binary_sensor.py b/custom_components/audiobookshelf/binary_sensor.py index bdac326..c79aea1 100644 --- a/custom_components/audiobookshelf/binary_sensor.py +++ b/custom_components/audiobookshelf/binary_sensor.py @@ -25,10 +25,15 @@ async def async_setup_entry( class AudiobookshelfBinarySensor(AudiobookshelfEntity, BinarySensorEntity): """audiobookshelf binary_sensor class.""" + @property + def unique_id(self) -> str: + """Return a unique ID to use for this entity.""" + return f"{DOMAIN}_connected" + @property def name(self) -> str: """Return the name of the binary_sensor.""" - return f"{DOMAIN}_connected" + return f"{DOMAIN} Connected" @property def device_class(self) -> str: diff --git a/custom_components/audiobookshelf/entity.py b/custom_components/audiobookshelf/entity.py index 0f64d7f..1469a32 100644 --- a/custom_components/audiobookshelf/entity.py +++ b/custom_components/audiobookshelf/entity.py @@ -18,11 +18,6 @@ def __init__( super().__init__(coordinator) self.config_entry = config_entry - @property - def unique_id(self) -> str: - """Return a unique ID to use for this entity.""" - return self.config_entry.entry_id - @property def device_info(self) -> dict[str, Any]: return { diff --git a/custom_components/audiobookshelf/sensor.py b/custom_components/audiobookshelf/sensor.py index 9ec2348..cc1a848 100644 --- a/custom_components/audiobookshelf/sensor.py +++ b/custom_components/audiobookshelf/sensor.py @@ -27,6 +27,11 @@ async def async_setup_entry( class AudiobookshelfSessionsSensor(AudiobookshelfEntity): """audiobookshelf Sessions Sensor class.""" + @property + def unique_id(self) -> str: + """Return a unique ID to use for this entity.""" + return f"{DOMAIN}_sessions" + @property def name(self) -> str: """Return the name of the sensor.""" @@ -66,6 +71,11 @@ def device_class(self) -> str: class AudiobookshelfNumberOfLibrariesSensor(AudiobookshelfEntity): """audiobookshelf Number of Libraries Sensor class.""" + @property + def unique_id(self) -> str: + """Return a unique ID to use for this entity.""" + return f"{DOMAIN}_libraries" + @property def name(self) -> str: """Return the name of the sensor.""" diff --git a/info.md b/info.md index 7f55596..06bad6f 100644 --- a/info.md +++ b/info.md @@ -11,7 +11,7 @@ | Entity | Type | Description | | --------------- | ---------------- | ------------------------------------ | | `connectivity` | `binary_sensor` | Show whether the server is connected | -| `open_sessions` | `sensor` | Show number of open audio sessions | +| `sessions` | `sensor` | Show number of open audio sessions | | `libraries` | `sensor` | Number of libraries on the server | {% if not installed %} From 4e8194c7bdd6113b09a11d454448b4e327b542b6 Mon Sep 17 00:00:00 2001 From: jadon <16850875+wolffshots@users.noreply.github.com> Date: Wed, 24 Apr 2024 20:48:45 +0200 Subject: [PATCH 05/20] chore: simplified sensors --- .../audiobookshelf/binary_sensor.py | 24 +++------ custom_components/audiobookshelf/sensor.py | 50 ++++--------------- 2 files changed, 17 insertions(+), 57 deletions(-) diff --git a/custom_components/audiobookshelf/binary_sensor.py b/custom_components/audiobookshelf/binary_sensor.py index c79aea1..303cfcc 100644 --- a/custom_components/audiobookshelf/binary_sensor.py +++ b/custom_components/audiobookshelf/binary_sensor.py @@ -1,7 +1,7 @@ """Binary sensor platform for Audiobookshelf.""" import logging -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import BinarySensorDeviceClass, BinarySensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -15,30 +15,20 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_devices: AddEntitiesCallback, + async_add_entities: AddEntitiesCallback, ) -> None: """Setup binary_sensor platform.""" coordinator = hass.data[DOMAIN][entry.entry_id] - async_add_devices([AudiobookshelfBinarySensor(coordinator, entry)]) + async_add_entities([AudiobookshelfBinarySensor(coordinator, entry)]) class AudiobookshelfBinarySensor(AudiobookshelfEntity, BinarySensorEntity): """audiobookshelf binary_sensor class.""" - @property - def unique_id(self) -> str: - """Return a unique ID to use for this entity.""" - return f"{DOMAIN}_connected" - - @property - def name(self) -> str: - """Return the name of the binary_sensor.""" - return f"{DOMAIN} Connected" - - @property - def device_class(self) -> str: - """Return the class of this binary_sensor.""" - return "connectivity" + _attr_name = f"{DOMAIN} Connected" + _attr_device_class = BinarySensorDeviceClass.CONNECTIVITY + _attr_icon ="mdi:format-quote-close" + entity_id = f"{DOMAIN}_connected" @property def is_on(self) -> bool: diff --git a/custom_components/audiobookshelf/sensor.py b/custom_components/audiobookshelf/sensor.py index cc1a848..7e8fc25 100644 --- a/custom_components/audiobookshelf/sensor.py +++ b/custom_components/audiobookshelf/sensor.py @@ -14,11 +14,11 @@ async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, - async_add_devices: AddEntitiesCallback, + async_add_entities: AddEntitiesCallback, ) -> None: """Setup sensor platform.""" coordinator = hass.data[DOMAIN][entry.entry_id] - async_add_devices([ + async_add_entities([ AudiobookshelfSessionsSensor(coordinator, entry), AudiobookshelfNumberOfLibrariesSensor(coordinator, entry), ]) @@ -27,15 +27,10 @@ async def async_setup_entry( class AudiobookshelfSessionsSensor(AudiobookshelfEntity): """audiobookshelf Sessions Sensor class.""" - @property - def unique_id(self) -> str: - """Return a unique ID to use for this entity.""" - return f"{DOMAIN}_sessions" - - @property - def name(self) -> str: - """Return the name of the sensor.""" - return f"{DOMAIN} Sessions" + _attr_name = f"{DOMAIN} Sessions" + _attr_device_class = f"{DOMAIN}__custom_device_class" + _attr_icon ="mdi:format-quote-close" + entity_id = f"{DOMAIN}_sessions" @property def state(self) -> int | None: @@ -58,28 +53,13 @@ def state(self) -> int | None: ) return None - @property - def icon(self) -> str: - """Return the icon of the sensor.""" - return "mdi:format-quote-close" - - @property - def device_class(self) -> str: - """Return device class of the sensor.""" - return "audiobookshelf__custom_device_class" - class AudiobookshelfNumberOfLibrariesSensor(AudiobookshelfEntity): """audiobookshelf Number of Libraries Sensor class.""" - @property - def unique_id(self) -> str: - """Return a unique ID to use for this entity.""" - return f"{DOMAIN}_libraries" - - @property - def name(self) -> str: - """Return the name of the sensor.""" - return f"{DOMAIN} Number of Libraries" + _attr_name = f"{DOMAIN} Libraries" + _attr_device_class = f"{DOMAIN}__custom_device_class" + _attr_icon ="mdi:format-quote-close" + entity_id = f"{DOMAIN}_libraries" @property def state(self) -> int | None: @@ -102,13 +82,3 @@ def state(self) -> int | None: "sensor: AttributeError caught while accessing coordinator data.", ) return None - - @property - def icon(self) -> str: - """Return the icon of the sensor.""" - return "mdi:format-quote-close" - - @property - def device_class(self) -> str: - """Return device class of the sensor.""" - return "audiobookshelf__custom_device_class" From a3b4e248108e46d19fa27e7dd3d2d542443948d8 Mon Sep 17 00:00:00 2001 From: jadon <16850875+wolffshots@users.noreply.github.com> Date: Wed, 24 Apr 2024 21:02:21 +0200 Subject: [PATCH 06/20] chore: add platform to entity ids --- custom_components/audiobookshelf/binary_sensor.py | 2 +- custom_components/audiobookshelf/sensor.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/custom_components/audiobookshelf/binary_sensor.py b/custom_components/audiobookshelf/binary_sensor.py index 303cfcc..825b0ba 100644 --- a/custom_components/audiobookshelf/binary_sensor.py +++ b/custom_components/audiobookshelf/binary_sensor.py @@ -28,7 +28,7 @@ class AudiobookshelfBinarySensor(AudiobookshelfEntity, BinarySensorEntity): _attr_name = f"{DOMAIN} Connected" _attr_device_class = BinarySensorDeviceClass.CONNECTIVITY _attr_icon ="mdi:format-quote-close" - entity_id = f"{DOMAIN}_connected" + entity_id = f"binary_sensor.{DOMAIN}_connected" @property def is_on(self) -> bool: diff --git a/custom_components/audiobookshelf/sensor.py b/custom_components/audiobookshelf/sensor.py index 7e8fc25..bb06377 100644 --- a/custom_components/audiobookshelf/sensor.py +++ b/custom_components/audiobookshelf/sensor.py @@ -30,7 +30,7 @@ class AudiobookshelfSessionsSensor(AudiobookshelfEntity): _attr_name = f"{DOMAIN} Sessions" _attr_device_class = f"{DOMAIN}__custom_device_class" _attr_icon ="mdi:format-quote-close" - entity_id = f"{DOMAIN}_sessions" + entity_id = f"sensor.{DOMAIN}_sessions" @property def state(self) -> int | None: @@ -59,7 +59,7 @@ class AudiobookshelfNumberOfLibrariesSensor(AudiobookshelfEntity): _attr_name = f"{DOMAIN} Libraries" _attr_device_class = f"{DOMAIN}__custom_device_class" _attr_icon ="mdi:format-quote-close" - entity_id = f"{DOMAIN}_libraries" + entity_id = f"sensor.{DOMAIN}_libraries" @property def state(self) -> int | None: From aeb035a3ad8dfed5070fbfaf47bd6aecbe1fbeb6 Mon Sep 17 00:00:00 2001 From: jadon <16850875+wolffshots@users.noreply.github.com> Date: Wed, 24 Apr 2024 21:30:12 +0200 Subject: [PATCH 07/20] chore: add init to sensors --- .../audiobookshelf/binary_sensor.py | 10 ++++++---- custom_components/audiobookshelf/sensor.py | 20 +++++++++++-------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/custom_components/audiobookshelf/binary_sensor.py b/custom_components/audiobookshelf/binary_sensor.py index 825b0ba..eff5fb2 100644 --- a/custom_components/audiobookshelf/binary_sensor.py +++ b/custom_components/audiobookshelf/binary_sensor.py @@ -25,10 +25,12 @@ async def async_setup_entry( class AudiobookshelfBinarySensor(AudiobookshelfEntity, BinarySensorEntity): """audiobookshelf binary_sensor class.""" - _attr_name = f"{DOMAIN} Connected" - _attr_device_class = BinarySensorDeviceClass.CONNECTIVITY - _attr_icon ="mdi:format-quote-close" - entity_id = f"binary_sensor.{DOMAIN}_connected" + def __init__(self) -> None: + self._attr_name = f"{DOMAIN} Connected" + self._attr_device_class = BinarySensorDeviceClass.CONNECTIVITY + # self._attr_device_info = ... # For automatic device registration + self._attr_unique_id = f"binary_sensor.{DOMAIN}_connected" + self._attr_icon = "mdi:format-quote-close" @property def is_on(self) -> bool: diff --git a/custom_components/audiobookshelf/sensor.py b/custom_components/audiobookshelf/sensor.py index bb06377..4e2934c 100644 --- a/custom_components/audiobookshelf/sensor.py +++ b/custom_components/audiobookshelf/sensor.py @@ -27,10 +27,12 @@ async def async_setup_entry( class AudiobookshelfSessionsSensor(AudiobookshelfEntity): """audiobookshelf Sessions Sensor class.""" - _attr_name = f"{DOMAIN} Sessions" - _attr_device_class = f"{DOMAIN}__custom_device_class" - _attr_icon ="mdi:format-quote-close" - entity_id = f"sensor.{DOMAIN}_sessions" + def __init__(self) -> None: + self._attr_name = f"{DOMAIN} Sessions" + self._attr_device_class = f"{DOMAIN}__custom_device_class" + # self._attr_device_info = ... # For automatic device registration + self._attr_unique_id = f"sensor.{DOMAIN}_sessions" + self._attr_icon ="mdi:format-quote-close" @property def state(self) -> int | None: @@ -56,10 +58,12 @@ def state(self) -> int | None: class AudiobookshelfNumberOfLibrariesSensor(AudiobookshelfEntity): """audiobookshelf Number of Libraries Sensor class.""" - _attr_name = f"{DOMAIN} Libraries" - _attr_device_class = f"{DOMAIN}__custom_device_class" - _attr_icon ="mdi:format-quote-close" - entity_id = f"sensor.{DOMAIN}_libraries" + def __init__(self) -> None: + self._attr_name = f"{DOMAIN} Libraries" + self._attr_device_class = f"{DOMAIN}__custom_device_class" + # self._attr_device_info = ... # For automatic device registration + self._attr_unique_id = f"sensor.{DOMAIN}_libraries" + self._attr_icon ="mdi:format-quote-close" @property def state(self) -> int | None: From 70861da12544ec07855e36602e55149c10cd38d4 Mon Sep 17 00:00:00 2001 From: jadon <16850875+wolffshots@users.noreply.github.com> Date: Wed, 24 Apr 2024 21:40:36 +0200 Subject: [PATCH 08/20] chore: updated signature for init for sensors --- custom_components/audiobookshelf/binary_sensor.py | 4 +++- custom_components/audiobookshelf/sensor.py | 7 +++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/custom_components/audiobookshelf/binary_sensor.py b/custom_components/audiobookshelf/binary_sensor.py index eff5fb2..44057ac 100644 --- a/custom_components/audiobookshelf/binary_sensor.py +++ b/custom_components/audiobookshelf/binary_sensor.py @@ -5,6 +5,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN from .entity import AudiobookshelfEntity @@ -25,12 +26,13 @@ async def async_setup_entry( class AudiobookshelfBinarySensor(AudiobookshelfEntity, BinarySensorEntity): """audiobookshelf binary_sensor class.""" - def __init__(self) -> None: + def __init__(self, coordinator: CoordinatorEntity, entry: ConfigEntry) -> None: self._attr_name = f"{DOMAIN} Connected" self._attr_device_class = BinarySensorDeviceClass.CONNECTIVITY # self._attr_device_info = ... # For automatic device registration self._attr_unique_id = f"binary_sensor.{DOMAIN}_connected" self._attr_icon = "mdi:format-quote-close" + super().__init__(coordinator, entry) @property def is_on(self) -> bool: diff --git a/custom_components/audiobookshelf/sensor.py b/custom_components/audiobookshelf/sensor.py index 4e2934c..73a38c9 100644 --- a/custom_components/audiobookshelf/sensor.py +++ b/custom_components/audiobookshelf/sensor.py @@ -4,6 +4,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import DOMAIN from .entity import AudiobookshelfEntity @@ -27,12 +28,13 @@ async def async_setup_entry( class AudiobookshelfSessionsSensor(AudiobookshelfEntity): """audiobookshelf Sessions Sensor class.""" - def __init__(self) -> None: + def __init__(self, coordinator: CoordinatorEntity, entry: ConfigEntry) -> None: self._attr_name = f"{DOMAIN} Sessions" self._attr_device_class = f"{DOMAIN}__custom_device_class" # self._attr_device_info = ... # For automatic device registration self._attr_unique_id = f"sensor.{DOMAIN}_sessions" self._attr_icon ="mdi:format-quote-close" + super().__init__(coordinator, entry) @property def state(self) -> int | None: @@ -58,12 +60,13 @@ def state(self) -> int | None: class AudiobookshelfNumberOfLibrariesSensor(AudiobookshelfEntity): """audiobookshelf Number of Libraries Sensor class.""" - def __init__(self) -> None: + def __init__(self, coordinator: CoordinatorEntity, entry: ConfigEntry) -> None: self._attr_name = f"{DOMAIN} Libraries" self._attr_device_class = f"{DOMAIN}__custom_device_class" # self._attr_device_info = ... # For automatic device registration self._attr_unique_id = f"sensor.{DOMAIN}_libraries" self._attr_icon ="mdi:format-quote-close" + super().__init__(coordinator, entry) @property def state(self) -> int | None: From e49e32a29dac347de4e47838a25b82d771705652 Mon Sep 17 00:00:00 2001 From: jadon <16850875+wolffshots@users.noreply.github.com> Date: Fri, 24 May 2024 17:30:43 +0200 Subject: [PATCH 09/20] chore!: rewrite it all --- custom_components/audiobookshelf/__init__.py | 186 ++------- custom_components/audiobookshelf/api.py | 107 ------ .../audiobookshelf/binary_sensor.py | 51 --- .../audiobookshelf/config_flow.py | 143 ------- custom_components/audiobookshelf/const.py | 19 - custom_components/audiobookshelf/entity.py | 37 -- .../audiobookshelf/manifest.json | 15 +- custom_components/audiobookshelf/sensor.py | 352 ++++++++++++++---- .../audiobookshelf/translations/en.json | 30 -- pyproject.toml | 3 +- tests/__init__.py | 1 - tests/conftest.py | 60 --- tests/const.py | 10 - tests/test_api.py | 221 ----------- tests/test_binary_sensor.py | 96 ----- tests/test_config_flow.py | 173 --------- tests/test_entity.py | 49 --- tests/test_init.py | 181 --------- tests/test_sensor.py | 115 ------ 19 files changed, 308 insertions(+), 1541 deletions(-) delete mode 100644 custom_components/audiobookshelf/api.py delete mode 100644 custom_components/audiobookshelf/binary_sensor.py delete mode 100644 custom_components/audiobookshelf/config_flow.py delete mode 100644 custom_components/audiobookshelf/const.py delete mode 100644 custom_components/audiobookshelf/entity.py delete mode 100644 custom_components/audiobookshelf/translations/en.json delete mode 100644 tests/__init__.py delete mode 100644 tests/conftest.py delete mode 100644 tests/const.py delete mode 100644 tests/test_api.py delete mode 100644 tests/test_binary_sensor.py delete mode 100644 tests/test_config_flow.py delete mode 100644 tests/test_entity.py delete mode 100644 tests/test_init.py delete mode 100644 tests/test_sensor.py diff --git a/custom_components/audiobookshelf/__init__.py b/custom_components/audiobookshelf/__init__.py index 6b23898..1a76984 100644 --- a/custom_components/audiobookshelf/__init__.py +++ b/custom_components/audiobookshelf/__init__.py @@ -1,172 +1,32 @@ -"""Init for audiobookshelf integration""" - -import asyncio +"""Custom component for Audiobookshelf.""" import logging +from homeassistant.helpers import discovery +# from .sensor import async_refresh_libraries -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import Config, HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady -from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator -from requests import HTTPError, Timeout - -from custom_components.audiobookshelf.api import AudiobookshelfApiClient - -from .const import ( - CONF_ACCESS_TOKEN, - CONF_HOST, - DOMAIN, - ISSUE_URL, - PLATFORMS, - SCAN_INTERVAL, - VERSION, -) - -_LOGGER: logging.Logger = logging.getLogger(__package__) - - -class AudiobookshelfDataUpdateCoordinator(DataUpdateCoordinator): - """Class to manage fetching data from the API.""" - - def __init__( - self, - hass: HomeAssistant, - client: AudiobookshelfApiClient, - ) -> None: - """Initialize.""" - self.api = client - self.platforms = [] - - super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL) - - async def _async_update_data(self) -> dict[str, None]: - """Update data via library.""" - update = {"connectivity": None, "users": None, "sessions": None} - try: - connectivity_update = await self.api.api_wrapper( - method="get", - url=self.api.get_host() + "/ping", - ) - _LOGGER.debug( - """async_update connectivity_update: %s""", - connectivity_update, - ) - update["connectivity"] = connectivity_update - except ConnectionError: - update["connectivity"] = "ConnectionError: Unable to connect." - except (TimeoutError, Timeout): - update["connectivity"] = "TimeoutError: Request timed out." - except HTTPError as http_error: - update["connectivity"] = f"HTTPError: Generic HTTP Error happened {http_error}" - try: - users_update = await self.api.api_wrapper( - method="get", - url=self.api.get_host() + "/api/users", - ) - num_users = self.api.count_active_users(users_update) - _LOGGER.debug("""async_update num_users: %s""", num_users) - update["users"] = num_users - except ConnectionError: - update["users"] = "ConnectionError: Unable to connect." - except (TimeoutError, Timeout): - update["users"] = "TimeoutError: Request timed out." - except HTTPError as http_error: - update["users"] = f"HTTPError: Generic HTTP Error happened {http_error}" - try: - online_users_update = await self.api.api_wrapper( - method="get", - url=self.api.get_host() + "/api/users/online", - ) - open_sessions = self.api.count_open_sessions(online_users_update) - _LOGGER.debug("""async_update open_sessions: %s""", open_sessions) - update["sessions"] = open_sessions - except ConnectionError: - update["sessions"] = "ConnectionError: Unable to connect." - except (TimeoutError, Timeout): - update["sessions"] = "TimeoutError: Request timed out." - except HTTPError as http_error: - update["sessions"] = f"HTTPError: Generic HTTP Error happened {http_error}" - try: - library_stats_update = await self.api.api_wrapper( - method="get", - url=self.api.get_host() + "/api/libraries", - ) - update["libraries"] = library_stats_update - except ConnectionError: - update["libraries"] = "ConnectionError: Unable to connect." - except (TimeoutError, Timeout): - update["libraries"] = "TimeoutError: Request timed out." - except HTTPError as http_error: - update["libraries"] = f"HTTPError: Generic HTTP Error happened {http_error}" - return update - -async def async_setup(hass: HomeAssistant, config: Config) -> bool: - """Setting up this integration using YAML is not supported.""" - return True - -async def async_setup_entry( - hass: HomeAssistant, - entry: ConfigEntry, -) -> bool: - """Set up this integration using UI.""" - if hass.data.get(DOMAIN) is None: - hass.data.setdefault(DOMAIN, {}) - _LOGGER.info( - """ - ------------------------------------------------------------------- - Audiobookshelf - Version: %s - This is a custom integration! - If you have any issues with this you need to open an issue here: - %s - ------------------------------------------------------------------- - """, - VERSION, - ISSUE_URL, - ) - - host = entry.data.get(CONF_HOST) - access_token = entry.data.get(CONF_ACCESS_TOKEN) - - session = async_get_clientsession(hass) - client = AudiobookshelfApiClient(host, access_token, session) - - coordinator = AudiobookshelfDataUpdateCoordinator(hass=hass, client=client) - await coordinator.async_refresh() +DOMAIN = "audiobookshelf" - if not coordinator.last_update_success: - raise ConfigEntryNotReady +_LOGGER = logging.getLogger(__name__) - hass.data[DOMAIN][entry.entry_id] = coordinator +# async def async_setup(hass, config): +# """Set up the Audiobookshelf component.""" +# # Schedule the setup of sensor platform +# hass.async_create_task(discovery.async_load_platform(hass, "sensor", DOMAIN, {}, config)) +# hass.async_create_task(async_refresh_libraries(hass)) - for platform in PLATFORMS: - if entry.options.get(platform, True): - coordinator.platforms.append(platform) - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(entry, platform), - ) - entry.add_update_listener(async_reload_entry) - return True +# return True +async def async_setup(hass, config): + """Set up the Audiobookshelf component.""" + # Schedule the setup of sensor platform + hass.async_create_task(discovery.async_load_platform(hass, "sensor", DOMAIN, {}, config)) -async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: - """Handle removal of an entry.""" - coordinator = hass.data[DOMAIN][entry.entry_id] - unloaded = all( - await asyncio.gather( - *[ - hass.config_entries.async_forward_entry_unload(entry, platform) - for platform in PLATFORMS - if platform in coordinator.platforms - ], - ), - ) - if unloaded: - hass.data[DOMAIN].pop(entry.entry_id) + # Use a helper to get the async_add_entities function from the sensor platform setup + # async def platform_setup(): + # """Wait for platform to be set up and then start refreshing libraries.""" + # platform = hass.data.get('sensor_platform') + # if platform: + # await async_refresh_libraries(hass, platform.async_add_entities) - return unloaded + # hass.async_create_task(platform_setup()) -async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: - """Reload config entry.""" - await async_unload_entry(hass, entry) - await async_setup_entry(hass, entry) + return True \ No newline at end of file diff --git a/custom_components/audiobookshelf/api.py b/custom_components/audiobookshelf/api.py deleted file mode 100644 index 2647f73..0000000 --- a/custom_components/audiobookshelf/api.py +++ /dev/null @@ -1,107 +0,0 @@ -"""Sample API Client.""" -import asyncio -import logging -import socket - -import aiohttp - -TIMEOUT = 10 - - -_LOGGER: logging.Logger = logging.getLogger(__package__) - -HEADERS = {"Content-type": "application/json; charset=UTF-8"} - - -class AudiobookshelfApiClient: - """API Client for communicating with Audiobookshelf server""" - - def __init__( - self, - host: str, - access_token: str, - session: aiohttp.ClientSession, - ) -> None: - """Sample API Client.""" - self._host = host - self._access_token = access_token - self._session = session - - def get_host(self) -> str: - """Getter for host var""" - return self._host - - def count_active_users(self, data: dict) -> int: - """ - Takes in an object with an array of users - and counts the active ones minus - the dummy hass one - """ - count = 0 - for user in data["users"]: - if user["isActive"] and user["username"] != "hass": - if ( - self._access_token is not None - and "token" in user - and user["token"] == self._access_token - ): - continue # Skip user with provided access_token - count += 1 - return count - - def count_open_sessions(self, data: dict) -> int: - """ - Counts the number of open stream sessions - """ - return len(data["openSessions"]) - - async def api_wrapper( - self, - method: str, - url: str, - data: dict | None = None, - headers: dict | None = None, - ) -> dict: - """Get information from the API.""" - if headers is not None: - headers["Authorization"] = f"Bearer {self._access_token}" - else: - headers = {"Authorization": f"Bearer {self._access_token}"} - try: - async with asyncio.timeout(TIMEOUT): # loop=asyncio.get_event_loop() - if method == "get": - response = await self._session.get(url, headers=headers) - if response.status >= 200 and response.status < 300: - return await response.json() - - if method == "put": - await self._session.put(url, headers=headers, json=data) - - elif method == "patch": - await self._session.patch(url, headers=headers, json=data) - - elif method == "post": - await self._session.post(url, headers=headers, json=data) - - except asyncio.TimeoutError as exception: - _LOGGER.error( - "Timeout error fetching information from %s - %s", - url, - exception, - ) - - except (KeyError, TypeError) as exception: - _LOGGER.error( - "Error parsing information from %s - %s", - url, - exception, - ) - except (aiohttp.ClientError, socket.gaierror) as exception: - _LOGGER.error( - "Error fetching information from %s - %s", - url, - exception, - ) - except Exception as exception: - _LOGGER.error("Something really wrong happened! - %s", exception) - raise exception diff --git a/custom_components/audiobookshelf/binary_sensor.py b/custom_components/audiobookshelf/binary_sensor.py deleted file mode 100644 index 44057ac..0000000 --- a/custom_components/audiobookshelf/binary_sensor.py +++ /dev/null @@ -1,51 +0,0 @@ -"""Binary sensor platform for Audiobookshelf.""" -import logging - -from homeassistant.components.binary_sensor import BinarySensorDeviceClass, BinarySensorEntity -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity - -from .const import DOMAIN -from .entity import AudiobookshelfEntity - -_LOGGER: logging.Logger = logging.getLogger(__package__) - - -async def async_setup_entry( - hass: HomeAssistant, - entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Setup binary_sensor platform.""" - coordinator = hass.data[DOMAIN][entry.entry_id] - async_add_entities([AudiobookshelfBinarySensor(coordinator, entry)]) - - -class AudiobookshelfBinarySensor(AudiobookshelfEntity, BinarySensorEntity): - """audiobookshelf binary_sensor class.""" - - def __init__(self, coordinator: CoordinatorEntity, entry: ConfigEntry) -> None: - self._attr_name = f"{DOMAIN} Connected" - self._attr_device_class = BinarySensorDeviceClass.CONNECTIVITY - # self._attr_device_info = ... # For automatic device registration - self._attr_unique_id = f"binary_sensor.{DOMAIN}_connected" - self._attr_icon = "mdi:format-quote-close" - super().__init__(coordinator, entry) - - @property - def is_on(self) -> bool: - """Return true if the binary_sensor is on.""" - try: - coordinator_get = self.coordinator.data.get("connectivity", "").get( - "success", - "", - ) - _LOGGER.debug("""binary_sensor coordinator got: %s""", coordinator_get) - return isinstance(coordinator_get, bool) and coordinator_get - except AttributeError: - _LOGGER.debug( - "binary_sensor: AttributeError caught while accessing coordinator data.", - ) - return False diff --git a/custom_components/audiobookshelf/config_flow.py b/custom_components/audiobookshelf/config_flow.py deleted file mode 100644 index 8c38dd7..0000000 --- a/custom_components/audiobookshelf/config_flow.py +++ /dev/null @@ -1,143 +0,0 @@ -"""Adds config flow for Audiobookshelf.""" -from __future__ import annotations - -import logging -from typing import Any - -import aiohttp -import voluptuous as vol -from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import callback -from homeassistant.data_entry_flow import FlowResult -from homeassistant.helpers.aiohttp_client import async_create_clientsession - -from .api import AudiobookshelfApiClient -from .const import CONF_ACCESS_TOKEN, CONF_HOST, DOMAIN, PLATFORMS - -_LOGGER: logging.Logger = logging.getLogger(__package__) - - -class AudiobookshelfFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): - """Config flow for audiobookshelf.""" - - VERSION = 1 - CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL - - def __init__(self) -> None: - """Initialize.""" - self._errors = {} - - async def async_step_user( - self, - user_input: dict[str, Any] | None = None, - ) -> FlowResult: - """Handle a flow initialized by the user.""" - self._errors = {} - - # Uncomment the next 2 lines if only a single instance of the integration is allowed: - # if self._async_current_entries(): - # return self.async_abort(reason="single_instance_allowed") - - if user_input is not None: - valid = await self._test_credentials( - user_input[CONF_HOST], - user_input[CONF_ACCESS_TOKEN], - ) - if valid: - return self.async_create_entry( - title=user_input[CONF_HOST], - data=user_input, - ) - self._errors["base"] = "auth" - - return await self._show_config_form(user_input) - - return await self._show_config_form(user_input) - - @staticmethod - @callback - def async_get_options_flow( - config_entry: ConfigEntry, - ) -> AudiobookshelfOptionsFlowHandler: - return AudiobookshelfOptionsFlowHandler(config_entry) - - async def _show_config_form( - self, - user_input: dict[str, Any] | None, # pylint: disable=unused-argument - ) -> FlowResult: - """Show the configuration form to edit location data.""" - return self.async_show_form( - step_id="user", - data_schema=vol.Schema( - {vol.Required(CONF_HOST): str, vol.Required(CONF_ACCESS_TOKEN): str}, - ), - errors=self._errors, - ) - - async def _test_credentials( - self, - host: str, - access_token: str, - ) -> bool: - """Return true if credentials is valid.""" - try: - session = async_create_clientsession(self.hass) - api = AudiobookshelfApiClient(host, access_token, session) - response = await api.api_wrapper( - method="get", - url=api.get_host() + "/api/users", - ) - _LOGGER.debug("""test_credentials response was: %s""", response) - if response: - return True - return False - except (ConnectionError, TimeoutError) as connection_or_timeout_error: - _LOGGER.debug("Connection or Timeout error: %s", connection_or_timeout_error) - return False - - except aiohttp.ClientResponseError as client_response_error: - _LOGGER.debug("ClientResponse Error: %s - %s", client_response_error.status, client_response_error.message) - return False - - -class AudiobookshelfOptionsFlowHandler(config_entries.OptionsFlow): - """Config flow options handler for audiobookshelf.""" - - def __init__(self, config_entry: ConfigEntry) -> None: - """Initialize HACS options flow.""" - self.config_entry = config_entry - self.options = dict(config_entry.options) - - async def async_step_init( - self, - user_input: dict[str, Any] | None = None, # pylint: disable=unused-argument - ) -> FlowResult: - """Manage the options.""" - return await self.async_step_user() - - async def async_step_user( - self, - user_input: dict[str, Any] | None = None, - ) -> FlowResult: - """Handle a flow initialized by the user.""" - if user_input is not None: - self.options.update(user_input) - return await self._update_options() - - return self.async_show_form( - step_id="user", - data_schema=vol.Schema( - { - vol.Required(x, default=self.options.get(x, True)): bool - for x in sorted(PLATFORMS) - }, - ), - ) - - async def _update_options(self) -> FlowResult: - """Update config entry options.""" - return self.async_create_entry( - title=self.config_entry.data.get(CONF_HOST), - data=self.options, - ) diff --git a/custom_components/audiobookshelf/const.py b/custom_components/audiobookshelf/const.py deleted file mode 100644 index c63a69d..0000000 --- a/custom_components/audiobookshelf/const.py +++ /dev/null @@ -1,19 +0,0 @@ -"""Constant for the Audiobookshelf integration""" - -# Base component constants -from datetime import timedelta - -NAME = "Audiobookshelf" -DOMAIN = "audiobookshelf" -DOMAIN_DATA = f"{DOMAIN}_data" -VERSION = "v0.1.0" - -ATTRIBUTION = "Server by https://www.audiobookshelf.org/" -ISSUE_URL = "https://github.com/wolffshots/hass-audiobookshelf/issues" - -SCAN_INTERVAL = timedelta(seconds=30) - -CONF_ACCESS_TOKEN = "access_token" -CONF_HOST = "host" - -PLATFORMS = ["binary_sensor", "sensor"] diff --git a/custom_components/audiobookshelf/entity.py b/custom_components/audiobookshelf/entity.py deleted file mode 100644 index 1469a32..0000000 --- a/custom_components/audiobookshelf/entity.py +++ /dev/null @@ -1,37 +0,0 @@ -"""AudiobookshelfEntity class""" -from typing import Any - -from homeassistant.config_entries import ConfigEntry -from homeassistant.helpers.update_coordinator import CoordinatorEntity - -from .const import ATTRIBUTION, DOMAIN, NAME, VERSION - - -class AudiobookshelfEntity(CoordinatorEntity): - """Extends the Coordinator Entity which handles polling""" - - def __init__( - self, - coordinator: CoordinatorEntity, - config_entry: ConfigEntry, - ) -> None: - super().__init__(coordinator) - self.config_entry = config_entry - - @property - def device_info(self) -> dict[str, Any]: - return { - "identifiers": {(DOMAIN, self.unique_id)}, - "name": NAME, - "model": VERSION, - "manufacturer": NAME, - } - - @property - def device_state_attributes(self) -> dict[str, str]: - """Return the state attributes.""" - return { - "attribution": ATTRIBUTION, - "id": str(self.coordinator.data.get("id")), - "integration": DOMAIN, - } diff --git a/custom_components/audiobookshelf/manifest.json b/custom_components/audiobookshelf/manifest.json index 344f5ad..ada0aab 100644 --- a/custom_components/audiobookshelf/manifest.json +++ b/custom_components/audiobookshelf/manifest.json @@ -1,12 +1,11 @@ { "domain": "audiobookshelf", "name": "Audiobookshelf", - "codeowners": ["@wolffshots"], - "config_flow": true, + "version": "1.0", + "documentation": "https://www.example.com", "dependencies": [], - "documentation": "https://github.com/wolffshots/hass-audiobookshelf", - "iot_class": "local_polling", - "issue_tracker": "https://github.com/wolffshots/hass-audiobookshelf/issues", - "requirements": [], - "version": "v0.1.0" -} + "codeowners": ["@your_github_username"], + "iot_class": "cloud_polling", + "integration_type": "device", + "requirements": ["aiohttp"] +} \ No newline at end of file diff --git a/custom_components/audiobookshelf/sensor.py b/custom_components/audiobookshelf/sensor.py index 73a38c9..33a0883 100644 --- a/custom_components/audiobookshelf/sensor.py +++ b/custom_components/audiobookshelf/sensor.py @@ -1,91 +1,291 @@ -"""Sensor platform for Audiobookshelf.""" + +import asyncio import logging +from typing import Any +import aiohttp +from datetime import timedelta +from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry, EntityRegistry -from homeassistant.config_entries import ConfigEntry +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.update_coordinator import ( + DataUpdateCoordinator, + UpdateFailed, +) from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback -from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN -from .entity import AudiobookshelfEntity +_LOGGER = logging.getLogger(__name__) -_LOGGER: logging.Logger = logging.getLogger(__package__) +DOMAIN = "audiobookshelf" +SCAN_INTERVAL = timedelta(seconds=10) +async def count_active_users(data: dict) -> int: + """ + Takes in an object with an array of users + and counts the active ones minus + the dummy hass one + """ + count = 0 + for user in data["users"]: + if user["isActive"] and user["username"] != "hass": + if ("token" in user and user["token"] == API_KEY): + continue # Skip user with provided access_token + count += 1 + return count -async def async_setup_entry( - hass: HomeAssistant, - entry: ConfigEntry, - async_add_entities: AddEntitiesCallback, -) -> None: - """Setup sensor platform.""" - coordinator = hass.data[DOMAIN][entry.entry_id] - async_add_entities([ - AudiobookshelfSessionsSensor(coordinator, entry), - AudiobookshelfNumberOfLibrariesSensor(coordinator, entry), - ]) +async def clean_user_attributes(data: dict): + """ + Removes the token and some extra data from users + """ + for user in data["users"]: + user["token"] = "" + return data +async def count_open_sessions(data: dict) -> int: + """ + Counts the number of open stream sessions + """ + return len(data["openSessions"]) -class AudiobookshelfSessionsSensor(AudiobookshelfEntity): - """audiobookshelf Sessions Sensor class.""" +async def count_libraries(data: dict) -> int: + """ + Counts the number libraries + """ + return len(data["libraries"]) - def __init__(self, coordinator: CoordinatorEntity, entry: ConfigEntry) -> None: - self._attr_name = f"{DOMAIN} Sessions" - self._attr_device_class = f"{DOMAIN}__custom_device_class" - # self._attr_device_info = ... # For automatic device registration - self._attr_unique_id = f"sensor.{DOMAIN}_sessions" - self._attr_icon ="mdi:format-quote-close" - super().__init__(coordinator, entry) +async def extract_library_details(data: dict) -> dict: + details = {} + for library in data.get('libraries', []): + details.update({library['id']: {"mediaType": library['mediaType'],"provider": library['provider']}}) + return details - @property - def state(self) -> int | None: - """Return the state of the sensor.""" +def get_total_duration(total_duration: float): + """Calculate the total duration in hours and round it to 0 decimal places.""" + return round(total_duration / 60 / 60, 0) + +def get_total_size(total_size: float): + return round(total_size / 1024 / 1024 / 1024, 2) + +async def fetch_library_stats(session, id): + """Fetch data from a single endpoint.""" + headers = {"Authorization": f"Bearer {API_KEY}"} + endpoint = f"api/libraries/{id}/stats" + try: + async with session.get(f"{API_URL}/{endpoint}", headers=headers) as response: + if response.status != 200: + _LOGGER.error(f"Failed to fetch data from {endpoint}, status: {response.status}") + return None + return await response.json() + except Exception as e: + _LOGGER.error(f"Exception occurred while fetching data from {endpoint}: {e}") + return None + +async def get_library_stats(data: dict) -> dict: + library_details = await extract_library_details(data) + async with aiohttp.ClientSession() as session: + results = {} + for id in library_details: + library_stats = await fetch_library_stats(session, id) + if isinstance(library_stats, Exception): + _LOGGER.error(f"Error fetching data: {library_stats}") + else: + # response for a decent library will be HUGE if we don't pick and choose bits + summary = {} + if library_details[id]["mediaType"] == "book": + summary.update({"totalAuthors":library_stats["totalAuthors"]}) + if library_stats["totalAuthors"] is not None: + summary.update({"totalAuthors":library_stats["totalAuthors"]}) + else: + summary.update({"totalAuthors": "0"}) + elif library_details[id]["mediaType"] == "podcast": + if library_stats["numAudioTracks"] is not None: + summary.update({"numAudioTracks":library_stats["numAudioTracks"]}) + else: + summary.update({"numAudioTracks": "0"}) + + if library_stats["totalItems"] is not None: + summary.update({"totalItems":library_stats["totalItems"]}) + else: + summary.update({"totalItems": "0"}) + + if library_stats["totalSize"] is not None: + summary.update({"totalSize": f"{get_total_size(library_stats["totalSize"])}GB"}) + else: + summary.update({"totalSize": "0 GB"}) + + if library_stats["totalDuration"] is not None: + summary.update({"totalDuration": f"{get_total_duration(library_stats["totalDuration"])} hours"}) + else: + summary.update({"totalDuration": "0 hours"}) + + results.update({id: summary}) + return results + +async def do_nothing(data): + return data + +type Sensor = dict[str, Any] + +# simple polling sensors +sensors: dict[str, Sensor] = { + "users": { + "endpoint": "api/users", + "name": "Audiobookshelf Users", + "data_function": count_active_users, + "attributes_function": clean_user_attributes + }, + "sessions": { + "endpoint": "api/users/online", + "name": "Audiobookshelf Open Sessions", + "data_function": count_open_sessions, + "attributes_function": do_nothing + }, + "libraries": { + "endpoint": "api/libraries", + "name": "Audiobookshelf Libraries", + "data_function": count_libraries, + "attributes_function": get_library_stats + }, +} + +# async def fetch_libraries(): +# headers = {"Authorization": f"Bearer {API_KEY}"} +# async with aiohttp.ClientSession() as session: +# async with session.get(f"{API_URL}/api/libraries", headers=headers) as response: +# return await response.json() + +# async def async_remove_sensors(hass: HomeAssistant, entity_type, sensor_type: str): +# """Remove all existing sensors of a specific type.""" +# entity_registry: EntityRegistry = async_get_entity_registry(hass) + +# entities_to_remove = [] + +# for entity in entity_registry.entities.values(): +# if entity.domain == entity_type: +# state = hass.states.get(entity.entity_id) +# if state and state.attributes.get("sensor_type") == sensor_type: +# entities_to_remove.append(entity.entity_id) + +# async def async_refresh_libraries(hass: HomeAssistant, async_add_entities): +# """Periodically refresh the sensors.""" +# while True: +# _LOGGER.error("Refreshing Audiobookshelf library stats") +# libraries_data = await fetch_libraries() +# # remove and re-add library sensors +# await async_remove_sensors(hass, "sensor", "audiobookshelf_library") +# coordinator = AudiobookshelfDataUpdateCoordinator(hass) +# await coordinator.async_config_entry_first_refresh() + +# ids = get_library_ids(libraries_data) +# entities = [] +# for id in ids: +# entities.append( +# AudiobookshelfSensor( +# coordinator, { +# "endpoint": f"api/libraries/{id}/stats", +# "name": f"Audiobookshelf Library {id}", +# "data_function": do_nothing, +# "attributes_function": do_nothing, +# "type": "audiobookshelf_library" +# }, +# ) +# ) +# async_add_entities(entities, True) +# await asyncio.sleep(60) # Wait for 1 minute before refreshing again + +async def async_setup_platform(hass: HomeAssistant, config, async_add_entities, discovery_info=None): + """Set up the sensor platform.""" + + coordinator = AudiobookshelfDataUpdateCoordinator(hass) + await coordinator.async_config_entry_first_refresh() + + entities = [ + AudiobookshelfSensor(coordinator, sensors["users"]), + AudiobookshelfSensor(coordinator, sensors["sessions"]), + AudiobookshelfSensor(coordinator, sensors["libraries"]) + ] + async_add_entities(entities, True) + +class AudiobookshelfDataUpdateCoordinator(DataUpdateCoordinator): + """Class to manage fetching Audiobookshelf data from the API.""" + + def __init__(self, hass: HomeAssistant): + """Initialize.""" + super().__init__( + hass, + _LOGGER, + name="audiobookshelf", + update_interval=SCAN_INTERVAL, + ) + + async def _async_update_data(self): + """Fetch data from API endpoint.""" + headers = {"Authorization": f"Bearer {API_KEY}"} + data = {} try: - coordinator_get = self.coordinator.data.get( - "sessions", - "", - ) - _LOGGER.debug("""sensor coordinator got: %s""", coordinator_get) - - if isinstance(coordinator_get, int): - return coordinator_get - - return None - - except AttributeError: - _LOGGER.debug( - "sensor: AttributeError caught while accessing coordinator data.", - ) - return None - -class AudiobookshelfNumberOfLibrariesSensor(AudiobookshelfEntity): - """audiobookshelf Number of Libraries Sensor class.""" - - def __init__(self, coordinator: CoordinatorEntity, entry: ConfigEntry) -> None: - self._attr_name = f"{DOMAIN} Libraries" - self._attr_device_class = f"{DOMAIN}__custom_device_class" - # self._attr_device_info = ... # For automatic device registration - self._attr_unique_id = f"sensor.{DOMAIN}_libraries" - self._attr_icon ="mdi:format-quote-close" - super().__init__(coordinator, entry) + async with aiohttp.ClientSession() as session: + for sensor in sensors: + async with session.get(f"{API_URL}/{sensors[sensor]["endpoint"]}", headers=headers) as response: + if response.status != 200: + raise UpdateFailed(f"Error fetching data: {response.status}") + data[sensors[sensor]["endpoint"]] = await response.json() + return data + except aiohttp.ClientError as err: + raise UpdateFailed(f"Error fetching data: {err}") + +class AudiobookshelfSensor(Entity): + """Representation of a sensor.""" + + def __init__(self, coordinator: AudiobookshelfDataUpdateCoordinator, sensor: Sensor): + """Initialize the sensor.""" + self._name = sensor["name"] + self._endpoint = sensor["endpoint"] + self.coordinator = coordinator + self._state = None + self._attributes = {} + self._process_data = sensor["data_function"] + self._process_attributes = sensor["attributes_function"] + + @property + def name(self): + """Return the name of the sensor.""" + return self._name @property - def state(self) -> int | None: + def state(self): """Return the state of the sensor.""" - try: - coordinator_get: dict | str = self.coordinator.data.get( - "libraries", - "", - ) - _LOGGER.debug("""sensor coordinator got: %s""", coordinator_get) - - if not isinstance(coordinator_get, str): - # count and return int - return len(coordinator_get["libraries"]) - - return None - - except AttributeError: - _LOGGER.debug( - "sensor: AttributeError caught while accessing coordinator data.", - ) - return None + return self._state + + @property + def extra_state_attributes(self): + """Return the state attributes.""" + return self._attributes + + @property + def device_info(self): + """Return device information about this entity.""" + return { + "identifiers": {(DOMAIN, "audiobookshelf_id")}, + "name": "Audiobookshelf", + "manufacturer": "My Company", + "model": "My Model", + "sw_version": "1.0", + } + + async def async_update(self): + """Fetch new state data for the sensor.""" + data = self.coordinator.data + if data: + endpoint_data = data.get(self._endpoint, {}) + if isinstance(endpoint_data, dict): + self._attributes.update(await self._process_attributes(endpoint_data)) + self._state = await self._process_data(data = endpoint_data) + else: + _LOGGER.error("Expected endpoint_data to be a dictionary, got %s", type(endpoint_data)) + _LOGGER.debug(f"Data: {endpoint_data}") + + async def async_added_to_hass(self): + """When entity is added to hass.""" + self.async_on_remove( + self.coordinator.async_add_listener(self.async_write_ha_state) + ) + diff --git a/custom_components/audiobookshelf/translations/en.json b/custom_components/audiobookshelf/translations/en.json deleted file mode 100644 index c8f3b8d..0000000 --- a/custom_components/audiobookshelf/translations/en.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "config": { - "step": { - "user": { - "title": "Audiobookshelf", - "description": "If you need help with the configuration have a look here: https://github.com/wolffshots/hass-audiobookshelf", - "data": { - "host": "Host", - "access_token": "Access Token" - } - } - }, - "error": { - "auth": "Auth failed." - }, - "abort": { - "single_instance_allowed": "Only a single instance is allowed." - } - }, - "options": { - "step": { - "user": { - "data": { - "binary_sensor": "Binary sensor enabled", - "sensor": "Sensor enabled" - } - } - } - } -} diff --git a/pyproject.toml b/pyproject.toml index c7ef7db..530f011 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,10 +49,11 @@ max-complexity = 15 [tool.poetry] name = "audiobookshelf" -version = "v0.1.0" +version = "v0.1.1" description = "Audiobookshelf HA custom component" authors = ["wolffshots <16850875+wolffshots@users.noreply.github.com>"] readme = "README.md" +package-mode = false [tool.poetry.group.dev.dependencies] pre-commit = "^3.3" diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index 1234966..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for Audiobookshelf integration.""" diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index f3c4bf1..0000000 --- a/tests/conftest.py +++ /dev/null @@ -1,60 +0,0 @@ -"""pytest fixtures.""" -from unittest.mock import patch - -import aiohttp -import pytest -from _pytest.fixtures import FixtureRequest -from requests import HTTPError - - -@pytest.fixture(autouse=True) -def auto_enable_custom_integrations(enable_custom_integrations: FixtureRequest) -> None: - """Enable custom integrations defined in the test dir.""" - yield - -# In this fixture, we are forcing calls to api_wrapper to raise an Exception. This is useful -# for exception handling. -@pytest.fixture(name="error_on_get_data") -def error_get_data_fixture() -> None: - """Simulate error when retrieving data from API.""" - with patch( - "custom_components.audiobookshelf.AudiobookshelfApiClient.api_wrapper", - side_effect=Exception, - ): - yield None - -@pytest.fixture(name="connectivity_error_on_get_data") -def connectivity_error_get_data_fixture() -> None: - """Simulate error when retrieving data from API.""" - with patch( - "custom_components.audiobookshelf.AudiobookshelfApiClient.api_wrapper", - side_effect=ConnectionError, - ): - yield None - -@pytest.fixture(name="timeout_error_on_get_data") -def timeout_error_get_data_fixture() -> None: - """Simulate error when retrieving data from API.""" - with patch( - "custom_components.audiobookshelf.AudiobookshelfApiClient.api_wrapper", - side_effect=TimeoutError, - ): - yield None - -@pytest.fixture(name="http_error_on_get_data") -def http_error_get_data_fixture() -> None: - """Simulate error when retrieving data from API.""" - with patch( - "custom_components.audiobookshelf.AudiobookshelfApiClient.api_wrapper", - side_effect=HTTPError, - ): - yield None - -@pytest.fixture(name="client_error_on_get_data") -def client_error_get_data_fixture() -> None: - """Simulate error when retrieving data from API.""" - with patch( - "custom_components.audiobookshelf.AudiobookshelfApiClient.api_wrapper", - side_effect=aiohttp.ClientResponseError(request_info=None, history=None), - ): - yield None diff --git a/tests/const.py b/tests/const.py deleted file mode 100644 index 4bc5473..0000000 --- a/tests/const.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Constants for Audiobookshelf tests.""" -from custom_components.audiobookshelf.const import ( - CONF_ACCESS_TOKEN, - CONF_HOST, -) - -MOCK_CONFIG = { - CONF_HOST: "some_host", - CONF_ACCESS_TOKEN: "some_access_token", -} diff --git a/tests/test_api.py b/tests/test_api.py deleted file mode 100644 index 0d3a4ba..0000000 --- a/tests/test_api.py +++ /dev/null @@ -1,221 +0,0 @@ -"""Tests for Audiobookshelf api.""" -import asyncio - -import aiohttp -import pytest -from _pytest.logging import LogCaptureFixture -from homeassistant.core import HomeAssistant -from homeassistant.helpers.aiohttp_client import async_get_clientsession -from pytest_homeassistant_custom_component.test_util.aiohttp import AiohttpClientMocker - -from custom_components.audiobookshelf.api import ( - AudiobookshelfApiClient, -) - - -async def test_api( - hass: HomeAssistant, - aioclient_mock: AiohttpClientMocker, - caplog: LogCaptureFixture, -) -> None: - """Test API calls.""" - - # To test the api submodule, we first create an instance of our API client - api = AudiobookshelfApiClient( - host="some_host", - access_token="some_access_token", - session=async_get_clientsession(hass), - ) - - caplog.clear() - aioclient_mock.clear_requests() - aioclient_mock.get("some_host", exc=asyncio.TimeoutError) - assert await api.api_wrapper("get", "some_host") is None - assert ( - len(caplog.record_tuples) == 1 - and "Timeout error fetching information from" in caplog.record_tuples[0][2] - ) - - caplog.clear() - aioclient_mock.clear_requests() - aioclient_mock.get("some_host", json={"test": "test"}) - assert (await api.api_wrapper("get", "some_host")) == {"test": "test"} - assert len(caplog.record_tuples) == 0 - - caplog.clear() - aioclient_mock.clear_requests() - aioclient_mock.put("some_host", exc=asyncio.TimeoutError) - assert await api.api_wrapper("put", "some_host") is None - assert ( - len(caplog.record_tuples) == 1 - and "Timeout error fetching information from" in caplog.record_tuples[0][2] - ) - - caplog.clear() - aioclient_mock.clear_requests() - aioclient_mock.patch("some_host", exc=asyncio.TimeoutError) - assert await api.api_wrapper("patch", "some_host") is None - assert ( - len(caplog.record_tuples) == 1 - and "Timeout error fetching information from" in caplog.record_tuples[0][2] - ) - - caplog.clear() - aioclient_mock.clear_requests() - aioclient_mock.post("some_host", exc=aiohttp.ClientError) - assert await api.api_wrapper("post", "some_host") is None - assert ( - len(caplog.record_tuples) == 1 - and "Error fetching information from" in caplog.record_tuples[0][2] - ) - - caplog.clear() - aioclient_mock.clear_requests() - aioclient_mock.post("some_host/2", exc=Exception) - with pytest.raises(Exception) as e_info: - assert await api.api_wrapper("post", "some_host/2") - assert e_info.errisinstance(Exception) - assert ( - len(caplog.record_tuples) == 1 - and "Something really wrong happened!" in caplog.record_tuples[0][2] - ) - - caplog.clear() - aioclient_mock.clear_requests() - aioclient_mock.post("some_host/3", exc=TypeError) - with pytest.raises(Exception) as e_info: - assert await api.api_wrapper("post", "some_host/3") is None - assert e_info.errisinstance(Exception) - assert ( - len(caplog.record_tuples) == 1 - and "Error parsing information from" in caplog.record_tuples[0][2] - ) - - caplog.clear() - aioclient_mock.clear_requests() - aioclient_mock.put("some_host", exc=asyncio.TimeoutError) - assert ( - await api.api_wrapper( - method="put", - url="some_host", - headers={"Test": "test header"}, - ) - is None - ) - assert ( - len(caplog.record_tuples) == 1 - and "Timeout error fetching information from" in caplog.record_tuples[0][2] - ) - - -async def test_api_helpers( - hass: HomeAssistant, - caplog: LogCaptureFixture, -) -> None: - """Test the functions that extract data from API responses""" - caplog.clear() - api = AudiobookshelfApiClient( - host="some_host", - access_token="some_access_token", - session=async_get_clientsession(hass), - ) - data = {"openSessions": [], "users": []} - assert api.count_open_sessions(data) == 0 - assert api.count_active_users(data) == 0 - data = { - "openSessions": [ - { - "bookId": "testing_session_1", - "chapters": "testing_session_1", - "coverPath": "testing_session_1", - "currentTime": "testing_session_1", - "date": "testing_session_1", - "dayOfWeek": "testing_session_1", - "deviceInfo": "testing_session_1", - "displayAuthor": "testing_session_1", - "displayTitle": "testing_session_1", - "duration": "testing_session_1", - "episodeId": "testing_session_1", - "id": "testing_session_1", - "libraryId": "testing_session_1", - "libraryItemId": "testing_session_1", - "mediaMetadata": "testing_session_1", - "mediaPlayer": "testing_session_1", - "mediaType": "testing_session_1", - "playMethod": "testing_session_1", - "serverVersion": "testing_session_1", - "startTime": "testing_session_1", - "startedAt": "testing_session_1", - "timeListening": "testing_session_1", - "updatedAt": "testing_session_1", - "userId": "testing_session_1", - }, - ], - "users": [ - { - "createdAt": "testing_user_1", - "id": "testing_user_1", - "isActive": True, - "isLocked": "testing_user_1", - "itemTagsSelected": "testing_user_1", - "lastSeen": "testing_user_1", - "librariesAccessible": "testing_user_1", - "oldUserId": "testing_user_1", - "permissions": "testing_user_1", - "seriesHideFromContinueListening": "testing_user_1", - "token": "testing_user_1", - "type": "testing_user_1", - "username": "testing_user_1", - }, - { - "createdAt": "testing_user_2", - "id": "testing_user_2", - "isActive": False, - "isLocked": "testing_user_2", - "itemTagsSelected": "testing_user_2", - "lastSeen": "testing_user_2", - "librariesAccessible": "testing_user_2", - "oldUserId": "testing_user_2", - "permissions": "testing_user_2", - "seriesHideFromContinueListening": "testing_user_2", - "token": "testing_user_2", - "type": "testing_user_2", - "username": "testing_user_2", - }, - { - "createdAt": "testing_user_3", - "id": "testing_user_3", - "isActive": True, - "isLocked": "testing_user_3", - "itemTagsSelected": "testing_user_3", - "lastSeen": "testing_user_3", - "librariesAccessible": "testing_user_3", - "oldUserId": "testing_user_3", - "permissions": "testing_user_3", - "seriesHideFromContinueListening": "testing_user_3", - "token": "some_access_token", - "type": "testing_user_3", - "username": "testing_user_3", - }, - { - "createdAt": "testing_user_4", - "id": "testing_user_4", - "isActive": True, - "isLocked": "testing_user_4", - "itemTagsSelected": "testing_user_4", - "lastSeen": "testing_user_4", - "librariesAccessible": "testing_user_4", - "oldUserId": "testing_user_4", - "permissions": "testing_user_4", - "seriesHideFromContinueListening": "testing_user_4", - "token": "testing_user_4", - "type": "testing_user_4", - "username": "hass", - }, - ], - } - assert api.count_open_sessions(data) == 1 - assert api.count_active_users(data) == 1 - - caplog.clear() - assert api.get_host() == "some_host" diff --git a/tests/test_binary_sensor.py b/tests/test_binary_sensor.py deleted file mode 100644 index 380e5b3..0000000 --- a/tests/test_binary_sensor.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Tests for Audiobookshelf binary sensor.""" -from unittest.mock import Mock, patch - -import pytest -from _pytest.logging import LogCaptureFixture -from homeassistant.core import HomeAssistant -from pytest_homeassistant_custom_component.common import MockConfigEntry - -from custom_components.audiobookshelf.binary_sensor import ( - AudiobookshelfBinarySensor, - async_setup_entry, -) -from custom_components.audiobookshelf.const import ( - DOMAIN, -) - -from .const import MOCK_CONFIG - - -@pytest.fixture(name="mock_coordinator") -async def mock_coordinator_fixture() -> Mock: - """Mock a coordinator for testing.""" - coordinator_mock = Mock() - coordinator_mock.data = { - "connectivity": { - "success": True, - }, - } - mock_coordinator_fixture.last_update_success = True - return coordinator_mock - - -@pytest.fixture(name="mock_coordinator_error") -async def mock_coordinator_error_fixture() -> None: - """Mock a coordinator error for testing.""" - with patch( - "custom_components.audiobookshelf.AudiobookshelfApiClient.api_wrapper", - side_effect=Exception, - ): - yield None - - -@pytest.mark.asyncio -async def test_binary_sensor_init_entry( - hass: HomeAssistant, mock_coordinator: Mock, -) -> None: - """Test the initialisation.""" - entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="sensors") - m_add_entities = Mock() - m_device = AudiobookshelfBinarySensor( - coordinator=mock_coordinator, - config_entry=entry, - ) - - hass.data[DOMAIN] = { - "sensors": {"audiobookshelf_connected": m_device}, - } - - await async_setup_entry(hass, entry, m_add_entities) - assert isinstance( - hass.data[DOMAIN]["sensors"]["audiobookshelf_connected"], - AudiobookshelfBinarySensor, - ) - m_add_entities.assert_called_once() - - -async def test_binary_sensor_properties(mock_coordinator: Mock) -> None: - """Test that the sensor returns the correct properties""" - config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="sensors") - sensor = AudiobookshelfBinarySensor( - coordinator=mock_coordinator, - config_entry=config_entry, - ) - assert sensor.name == "audiobookshelf_connected" - assert sensor.device_class == "connectivity" - assert sensor.is_on is True - - -async def test_binary_sensor_error( - mock_coordinator_error: Mock, caplog: LogCaptureFixture, -) -> None: - """Test for exception handling on exception on coordinator""" - caplog.clear() - config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="sensors") - sensor = AudiobookshelfBinarySensor( - coordinator=mock_coordinator_error, - config_entry=config_entry, - ) - assert sensor.name == "audiobookshelf_connected" - assert sensor.device_class == "connectivity" - assert sensor.is_on is False - assert len(caplog.record_tuples) == 1 - assert ( - "AttributeError caught while accessing coordinator data." - in caplog.record_tuples[0][2] - ) diff --git a/tests/test_config_flow.py b/tests/test_config_flow.py deleted file mode 100644 index b5e0f8e..0000000 --- a/tests/test_config_flow.py +++ /dev/null @@ -1,173 +0,0 @@ -"""Test Audiobookshelf config flow.""" -from unittest.mock import patch - -import pytest -from _pytest.fixtures import FixtureRequest -from homeassistant import config_entries, data_entry_flow -from homeassistant.core import HomeAssistant -from pytest_homeassistant_custom_component.common import MockConfigEntry -from pytest_homeassistant_custom_component.test_util.aiohttp import AiohttpClientMocker - -from custom_components.audiobookshelf.const import ( - DOMAIN, - PLATFORMS, -) - -from .const import MOCK_CONFIG - - -# This fixture bypasses the actual setup of the integration -# since we only want to test the config flow. We test the -# actual functionality of the integration in other test modules. -@pytest.fixture(autouse=True) -def bypass_setup_fixture() -> None: - """Prevent setup.""" - with patch("custom_components.audiobookshelf.async_setup", return_value=True), patch( - "custom_components.audiobookshelf.async_setup_entry", - return_value=True, - ): - yield - -# Here we simiulate a successful config flow from the backend. -# Note that we use the `bypass_get_data` fixture here because -# we want the config flow validation to succeed during the test. -async def test_successful_config_flow(hass:HomeAssistant, aioclient_mock: AiohttpClientMocker)-> None: - """Test a successful config flow.""" - - aioclient_mock.get("some_host/ping", json={"success": True}) - aioclient_mock.get("some_host/api/users", json={"users": []}) - aioclient_mock.get("some_host/api/users/online", json={"openSessions": []}) - - - # Initialize a config flow - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER}, - ) - - # Check that the config flow shows the user form as the first step - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "user" - - # If a user were to enter `some_host` for username and `test_password` - # for password, it would result in this function call - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input=MOCK_CONFIG, - ) - - # Check that the config flow is complete and a new entry is created with - # the input data - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "some_host" - assert result["data"] == MOCK_CONFIG - assert result["result"] - - aioclient_mock.clear_requests() - - -# In this case, we want to simulate a failure during the config flow. -# We use the `error_on_get_data` mock instead of `bypass_get_data` -# (note the function parameters) to raise an Exception during -# validation of the input config. -async def test_failed_config_flow(hass:HomeAssistant, aioclient_mock: AiohttpClientMocker)-> None: - """Test a failed config flow due to credential validation failure.""" - aioclient_mock.get("some_host/ping", json={"success": True}) - aioclient_mock.get("some_host/api/users", status=404) - aioclient_mock.get("some_host/api/users/online", status=404) - - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER}, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "user" - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input=MOCK_CONFIG, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "auth"} - - aioclient_mock.clear_requests() - - -async def test_timeout_error_config_flow(hass: HomeAssistant, timeout_error_on_get_data: FixtureRequest)-> None: - """Test a failed config flow due to credential validation failure.""" - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER}, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "user" - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input=MOCK_CONFIG, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "auth"} - -async def test_connectivity_error_config_flow(hass: HomeAssistant, connectivity_error_on_get_data:FixtureRequest)-> None: - """Test a failed config flow due to credential validation failure.""" - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER}, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "user" - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input=MOCK_CONFIG, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "auth"} - -async def test_client_error_config_flow(hass:HomeAssistant, client_error_on_get_data:FixtureRequest)-> None: - """Test a failed config flow due to credential validation failure.""" - - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": config_entries.SOURCE_USER}, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "user" - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], user_input=MOCK_CONFIG, - ) - - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["errors"] == {"base": "auth"} - -# Our config flow also has an options flow, so we must test it as well. -async def test_options_flow(hass:HomeAssistant)-> None: - """Test an options flow.""" - # Create a new MockConfigEntry and add to HASS (we're bypassing config - # flow entirely) - entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="test") - entry.add_to_hass(hass) - - # Initialize an options flow - await hass.config_entries.async_setup(entry.entry_id) - result = await hass.config_entries.options.async_init(entry.entry_id) - - # Verify that the first options step is a user form - assert result["type"] == data_entry_flow.RESULT_TYPE_FORM - assert result["step_id"] == "user" - - # Enter some fake data into the form - result = await hass.config_entries.options.async_configure( - result["flow_id"], - user_input={platform: platform != "sensor" for platform in PLATFORMS}, - ) - - # Verify that the flow finishes - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "some_host" - - # Verify that the options were updated - assert entry.options == {"binary_sensor": True, "sensor": False} diff --git a/tests/test_entity.py b/tests/test_entity.py deleted file mode 100644 index a3574f9..0000000 --- a/tests/test_entity.py +++ /dev/null @@ -1,49 +0,0 @@ -from unittest.mock import Mock - -import pytest -from pytest_homeassistant_custom_component.common import MockConfigEntry - -from custom_components.audiobookshelf.const import DOMAIN, VERSION -from custom_components.audiobookshelf.entity import AudiobookshelfEntity - -from .const import MOCK_CONFIG - - -@pytest.fixture(name="mock_coordinator") -async def mock_coordinator_fixture() -> Mock: - """Mock a coordinator for testing.""" - coordinator_mock = Mock() - coordinator_mock.data = {"sessions": 6} - mock_coordinator_fixture.last_update_success = True - return coordinator_mock - - -def test_unique_id(mock_coordinator: Mock) -> None: - """Test unique id response for entity""" - entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="audiobookshelf") - entity = AudiobookshelfEntity(coordinator=mock_coordinator, config_entry=entry) - assert entity.unique_id == "audiobookshelf" - - -def test_device_info(mock_coordinator: Mock) -> None: - """Test device info response for entity""" - entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="audiobookshelf") - entity = AudiobookshelfEntity(coordinator=mock_coordinator, config_entry=entry) - - assert entity.device_info == { - "identifiers": {("audiobookshelf", "audiobookshelf")}, - "manufacturer": "Audiobookshelf", - "model": VERSION, - "name": "Audiobookshelf", - } - - -def test_device_state_attributes(mock_coordinator: Mock) -> None: - """Test device state attributes response for entity""" - entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="audiobookshelf") - entity = AudiobookshelfEntity(coordinator=mock_coordinator, config_entry=entry) - assert entity.device_state_attributes == { - "attribution": "Server by https://www.audiobookshelf.org/", - "id": "None", - "integration": DOMAIN, - } diff --git a/tests/test_init.py b/tests/test_init.py deleted file mode 100644 index a51fbd7..0000000 --- a/tests/test_init.py +++ /dev/null @@ -1,181 +0,0 @@ -"""Test Audiobookshelf setup process.""" - -import pytest -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady -from pytest_homeassistant_custom_component.test_util.aiohttp import AiohttpClientMocker - -from custom_components.audiobookshelf import ( - AudiobookshelfDataUpdateCoordinator, - async_reload_entry, - async_setup, - async_setup_entry, - async_unload_entry, -) -from custom_components.audiobookshelf.const import ( - DOMAIN, -) - -from .const import MOCK_CONFIG - -pytest_plugins = "pytest_homeassistant_custom_component" - -config_entry = ConfigEntry( - domain=DOMAIN, - data=MOCK_CONFIG, - entry_id="test_entry_id_setup", - version=1, - title="Audiobookshelf", - source="some source", - minor_version=1, -) - -async def test_setup(hass: HomeAssistant)->None: - assert (await async_setup(hass, MOCK_CONFIG)) is True - -async def test_setup_entry( - hass: HomeAssistant, - aioclient_mock: AiohttpClientMocker, -) -> None: - aioclient_mock.get("some_host/ping", json={"success": True}) - aioclient_mock.get("some_host/api/users", json={"users": []}) - aioclient_mock.get("some_host/api/users/online", json={"openSessions": []}) - assert await async_setup_entry(hass, config_entry) - assert DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN] - assert isinstance( - hass.data["audiobookshelf"]["test_entry_id_setup"], - AudiobookshelfDataUpdateCoordinator, - ) - aioclient_mock.clear_requests() - -async def test_unload_entry( - hass: HomeAssistant, - aioclient_mock: AiohttpClientMocker, -) -> None: - aioclient_mock.get("some_host/ping", json={"success": True}) - aioclient_mock.get("some_host/api/users", json={"users": []}) - aioclient_mock.get("some_host/api/users/online", json={"openSessions": []}) - assert await async_setup_entry(hass, config_entry) - assert DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN] - assert isinstance( - hass.data[DOMAIN][config_entry.entry_id], - AudiobookshelfDataUpdateCoordinator, - ) - assert await async_unload_entry(hass, config_entry) - assert config_entry.entry_id not in hass.data[DOMAIN] - aioclient_mock.clear_requests() - - -async def test_setup_unload_and_reload_entry( - hass: HomeAssistant, - aioclient_mock: AiohttpClientMocker, -) -> None: - """Test entry setup and unload.""" - # Create a mock entry so we don't have to go through config flow - aioclient_mock.get("some_host/ping", json={"success": True}) - aioclient_mock.get("some_host/api/users", json={"users": []}) - aioclient_mock.get("some_host/api/users/online", json={"openSessions": []}) - - # Set up the entry and assert that the values set during setup are where we expect - # them to be. Because we have patched the AudiobookshelfDataUpdateCoordinator.async_get_data - # call, no code from custom_components/audiobookshelf/api.py actually runs. - assert await async_setup_entry(hass, config_entry) - assert DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN] - assert isinstance( - hass.data[DOMAIN][config_entry.entry_id], - AudiobookshelfDataUpdateCoordinator, - ) - assert await async_unload_entry(hass, config_entry) - assert config_entry.entry_id not in hass.data[DOMAIN] - - assert await async_setup_entry(hass, config_entry) - assert DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN] - assert isinstance( - hass.data[DOMAIN][config_entry.entry_id], - AudiobookshelfDataUpdateCoordinator, - ) - - # Reload the entry and assert that the data from above is still there - assert await async_reload_entry(hass, config_entry) is None - assert DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN] - assert isinstance( - hass.data[DOMAIN][config_entry.entry_id], - AudiobookshelfDataUpdateCoordinator, - ) - - # Unload the entry and verify that the data has been removed - assert await async_unload_entry(hass, config_entry) - assert config_entry.entry_id not in hass.data[DOMAIN] - - aioclient_mock.clear_requests() - - -async def test_setup_entry_exception( - hass: HomeAssistant, - error_on_get_data: None, # pylint: disable=unused-argument -) -> None: - """Test ConfigEntryNotReady when API raises an exception during entry setup.""" - # In this case we are testing the condition where async_setup_entry raises - # ConfigEntryNotReady using the `error_on_get_data` fixture which simulates - # an error. - with pytest.raises(ConfigEntryNotReady): - assert await async_setup_entry(hass, config_entry) - -async def test_setup_entry_connectivity_exception( - hass: HomeAssistant, - connectivity_error_on_get_data: None, # pylint: disable=unused-argument -) -> None: - """Test connectivity error response when API raises an exception during entry setup.""" - - assert await async_setup_entry(hass, config_entry) - assert DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN] - assert isinstance( - hass.data[DOMAIN][config_entry.entry_id], - AudiobookshelfDataUpdateCoordinator, - ) - assert hass.data[DOMAIN][config_entry.entry_id].data.get("connectivity", "") == "ConnectionError: Unable to connect." - assert hass.data[DOMAIN][config_entry.entry_id].data.get("users", "") == "ConnectionError: Unable to connect." - assert hass.data[DOMAIN][config_entry.entry_id].data.get("sessions", "") == "ConnectionError: Unable to connect." - - assert await async_unload_entry(hass, config_entry) - assert config_entry.entry_id not in hass.data[DOMAIN] - -async def test_setup_entry_timeout_exception( - hass: HomeAssistant, - timeout_error_on_get_data: None, # pylint: disable=unused-argument -) -> None: - """Test timeout error response when API raises an exception during entry setup.""" - - assert await async_setup_entry(hass, config_entry) - assert DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN] - assert isinstance( - hass.data[DOMAIN][config_entry.entry_id], - AudiobookshelfDataUpdateCoordinator, - ) - assert hass.data[DOMAIN][config_entry.entry_id].data.get("connectivity", "") == "TimeoutError: Request timed out." - assert hass.data[DOMAIN][config_entry.entry_id].data.get("users", "") == "TimeoutError: Request timed out." - assert hass.data[DOMAIN][config_entry.entry_id].data.get("sessions", "") == "TimeoutError: Request timed out." - - assert await async_unload_entry(hass, config_entry) - assert config_entry.entry_id not in hass.data[DOMAIN] - - -async def test_setup_entry_http_exception( - hass: HomeAssistant, - http_error_on_get_data: None, # pylint: disable=unused-argument -) -> None: - """Test http error response when API raises an exception during entry setup.""" - - assert await async_setup_entry(hass, config_entry) - assert DOMAIN in hass.data and config_entry.entry_id in hass.data[DOMAIN] - assert isinstance( - hass.data[DOMAIN][config_entry.entry_id], - AudiobookshelfDataUpdateCoordinator, - ) - assert hass.data[DOMAIN][config_entry.entry_id].data.get("connectivity", "") == "HTTPError: Generic HTTP Error happened " - assert hass.data[DOMAIN][config_entry.entry_id].data.get("users", "") == "HTTPError: Generic HTTP Error happened " - assert hass.data[DOMAIN][config_entry.entry_id].data.get("sessions", "") == "HTTPError: Generic HTTP Error happened " - - assert await async_unload_entry(hass, config_entry) - assert config_entry.entry_id not in hass.data[DOMAIN] diff --git a/tests/test_sensor.py b/tests/test_sensor.py deleted file mode 100644 index 6e83898..0000000 --- a/tests/test_sensor.py +++ /dev/null @@ -1,115 +0,0 @@ -"""Tests for Audiobookshelf sensor.""" -from unittest.mock import Mock, patch - -import pytest -from _pytest.logging import LogCaptureFixture -from homeassistant.core import HomeAssistant -from pytest_homeassistant_custom_component.common import MockConfigEntry - -from custom_components.audiobookshelf.const import ( - DOMAIN, -) -from custom_components.audiobookshelf.sensor import ( - AudiobookshelfSessionsSensor, - async_setup_entry, -) - -from .const import MOCK_CONFIG - - -@pytest.fixture(name="mock_coordinator") -async def mock_coordinator_fixture() -> Mock: - """Mock a coordinator for testing.""" - coordinator_mock = Mock() - coordinator_mock.data = {"sessions": 6} - mock_coordinator_fixture.last_update_success = True - return coordinator_mock - - -@pytest.fixture(name="mock_coordinator_unknown") -async def mock_coordinator_unknown_fixture() -> Mock: - """Mock a coordinator for testing.""" - coordinator_mock = Mock() - coordinator_mock.data = {"sessions": "some other type"} - mock_coordinator_fixture.last_update_success = True - return coordinator_mock - - -@pytest.fixture(name="mock_coordinator_error") -async def mock_coordinator_error_fixture() -> None: - """Mock a coordinator error for testing.""" - with patch( - "custom_components.audiobookshelf.AudiobookshelfApiClient.api_wrapper", - side_effect=Exception, - ): - yield None - - -@pytest.mark.asyncio -async def test_sensor_init_entry( - hass: HomeAssistant, - mock_coordinator: Mock, -) -> None: - """Test the initialisation.""" - entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="sensors") - m_add_entities = Mock() - m_device = AudiobookshelfSessionsSensor( - coordinator=mock_coordinator, - config_entry=entry, - ) - - hass.data[DOMAIN] = { - "sensors": {"audiobookshelf_sessions": m_device}, - } - - await async_setup_entry(hass, entry, m_add_entities) - assert isinstance( - hass.data[DOMAIN]["sensors"]["audiobookshelf_sessions"], - AudiobookshelfSessionsSensor, - ) - m_add_entities.assert_called_once() - - -async def test_sensor_properties(mock_coordinator: Mock) -> None: - """Test that the sensor returns the correct properties""" - config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="sensors") - sensor = AudiobookshelfSessionsSensor( - coordinator=mock_coordinator, - config_entry=config_entry, - ) - assert sensor.name == "audiobookshelf_sessions" - assert sensor.device_class == "audiobookshelf__custom_device_class" - assert sensor.icon == "mdi:format-quote-close" - assert sensor.state == 6 - - -async def test_sensor_unknown(mock_coordinator_unknown: Mock) -> None: - """Test that the sensor returns the correct properties""" - config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="sensors") - sensor = AudiobookshelfSessionsSensor( - coordinator=mock_coordinator_unknown, - config_entry=config_entry, - ) - assert sensor.state is None - - -async def test_sensor_error( - mock_coordinator_error: Mock, - caplog: LogCaptureFixture, -) -> None: - """Test for exception handling on exception on coordinator""" - caplog.clear() - config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_CONFIG, entry_id="sensors") - sensor = AudiobookshelfSessionsSensor( - coordinator=mock_coordinator_error, - config_entry=config_entry, - ) - assert sensor.name == "audiobookshelf_sessions" - assert sensor.device_class == "audiobookshelf__custom_device_class" - assert sensor.icon == "mdi:format-quote-close" - assert sensor.state is None - assert len(caplog.record_tuples) == 1 - assert ( - "AttributeError caught while accessing coordinator data." - in caplog.record_tuples[0][2] - ) From 57272d380c9f9d5b77d0b210290cc05928345df1 Mon Sep 17 00:00:00 2001 From: jadon <16850875+wolffshots@users.noreply.github.com> Date: Fri, 24 May 2024 18:21:13 +0200 Subject: [PATCH 10/20] feat: set api key and url from config.yaml --- custom_components/audiobookshelf/__init__.py | 52 ++++++++++++-------- custom_components/audiobookshelf/sensor.py | 12 +++++ 2 files changed, 44 insertions(+), 20 deletions(-) diff --git a/custom_components/audiobookshelf/__init__.py b/custom_components/audiobookshelf/__init__.py index 1a76984..a60d330 100644 --- a/custom_components/audiobookshelf/__init__.py +++ b/custom_components/audiobookshelf/__init__.py @@ -1,32 +1,44 @@ """Custom component for Audiobookshelf.""" import logging -from homeassistant.helpers import discovery -# from .sensor import async_refresh_libraries +import voluptuous as vol +from homeassistant.helpers import config_validation as cv, discovery DOMAIN = "audiobookshelf" -_LOGGER = logging.getLogger(__name__) - -# async def async_setup(hass, config): -# """Set up the Audiobookshelf component.""" -# # Schedule the setup of sensor platform -# hass.async_create_task(discovery.async_load_platform(hass, "sensor", DOMAIN, {}, config)) -# hass.async_create_task(async_refresh_libraries(hass)) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required("api_key"): cv.string, + vol.Required("api_url"): cv.string, + vol.Optional("scan_interval", default=300): cv.positive_int + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -# return True +_LOGGER = logging.getLogger(__name__) async def async_setup(hass, config): """Set up the Audiobookshelf component.""" - # Schedule the setup of sensor platform + conf = config.get(DOMAIN) + if conf is None: + _LOGGER.error(f"No config found for {DOMAIN}!") + return True + api_key = conf["api_key"] + api_url = conf["api_url"] + scan_interval = conf["scan_interval"] + + _LOGGER.info("API URL: %s", api_url) + _LOGGER.info("Scan Interval: %s", scan_interval) + + hass.data[DOMAIN] = { + "api_key": api_key, + "api_url": api_url, + "scan_interval": scan_interval + } + # Schedule the setup of sensor platform if needed hass.async_create_task(discovery.async_load_platform(hass, "sensor", DOMAIN, {}, config)) - # Use a helper to get the async_add_entities function from the sensor platform setup - # async def platform_setup(): - # """Wait for platform to be set up and then start refreshing libraries.""" - # platform = hass.data.get('sensor_platform') - # if platform: - # await async_refresh_libraries(hass, platform.async_add_entities) - - # hass.async_create_task(platform_setup()) - return True \ No newline at end of file diff --git a/custom_components/audiobookshelf/sensor.py b/custom_components/audiobookshelf/sensor.py index 33a0883..e6260e7 100644 --- a/custom_components/audiobookshelf/sensor.py +++ b/custom_components/audiobookshelf/sensor.py @@ -195,6 +195,18 @@ async def do_nothing(data): async def async_setup_platform(hass: HomeAssistant, config, async_add_entities, discovery_info=None): """Set up the sensor platform.""" + conf = hass.data.get(DOMAIN) + if conf is None: + _LOGGER.error("Configuration not found in hass.data") + return + + global API_URL + API_URL = conf["api_url"] + global API_KEY + API_KEY = conf["api_key"] + global SCAN_INTERVAL + SCAN_INTERVAL = timedelta(seconds=conf["scan_interval"]) + coordinator = AudiobookshelfDataUpdateCoordinator(hass) await coordinator.async_config_entry_first_refresh() From 1293f0d3586f79d9916a0044d85945d86b0fa1d3 Mon Sep 17 00:00:00 2001 From: jadon <16850875+wolffshots@users.noreply.github.com> Date: Fri, 24 May 2024 19:42:18 +0200 Subject: [PATCH 11/20] chore: some cleanup --- .../audiobookshelf/manifest.json | 13 +++--- custom_components/audiobookshelf/sensor.py | 45 ------------------- 2 files changed, 7 insertions(+), 51 deletions(-) diff --git a/custom_components/audiobookshelf/manifest.json b/custom_components/audiobookshelf/manifest.json index ada0aab..e94228b 100644 --- a/custom_components/audiobookshelf/manifest.json +++ b/custom_components/audiobookshelf/manifest.json @@ -1,11 +1,12 @@ { "domain": "audiobookshelf", "name": "Audiobookshelf", - "version": "1.0", - "documentation": "https://www.example.com", + "version": "0.1.1", + "documentation": "https://github.com/wolffshots/hass-audiobookshelf", + "iot_class": "local_polling", + "issue_tracker": "https://github.com/wolffshots/hass-audiobookshelf/issues", "dependencies": [], - "codeowners": ["@your_github_username"], - "iot_class": "cloud_polling", - "integration_type": "device", - "requirements": ["aiohttp"] + "requirements": ["aiohttp"], + "codeowners": ["@wolffshots"], + "integration_type": "device" } \ No newline at end of file diff --git a/custom_components/audiobookshelf/sensor.py b/custom_components/audiobookshelf/sensor.py index e6260e7..c682bd8 100644 --- a/custom_components/audiobookshelf/sensor.py +++ b/custom_components/audiobookshelf/sensor.py @@ -147,51 +147,6 @@ async def do_nothing(data): }, } -# async def fetch_libraries(): -# headers = {"Authorization": f"Bearer {API_KEY}"} -# async with aiohttp.ClientSession() as session: -# async with session.get(f"{API_URL}/api/libraries", headers=headers) as response: -# return await response.json() - -# async def async_remove_sensors(hass: HomeAssistant, entity_type, sensor_type: str): -# """Remove all existing sensors of a specific type.""" -# entity_registry: EntityRegistry = async_get_entity_registry(hass) - -# entities_to_remove = [] - -# for entity in entity_registry.entities.values(): -# if entity.domain == entity_type: -# state = hass.states.get(entity.entity_id) -# if state and state.attributes.get("sensor_type") == sensor_type: -# entities_to_remove.append(entity.entity_id) - -# async def async_refresh_libraries(hass: HomeAssistant, async_add_entities): -# """Periodically refresh the sensors.""" -# while True: -# _LOGGER.error("Refreshing Audiobookshelf library stats") -# libraries_data = await fetch_libraries() -# # remove and re-add library sensors -# await async_remove_sensors(hass, "sensor", "audiobookshelf_library") -# coordinator = AudiobookshelfDataUpdateCoordinator(hass) -# await coordinator.async_config_entry_first_refresh() - -# ids = get_library_ids(libraries_data) -# entities = [] -# for id in ids: -# entities.append( -# AudiobookshelfSensor( -# coordinator, { -# "endpoint": f"api/libraries/{id}/stats", -# "name": f"Audiobookshelf Library {id}", -# "data_function": do_nothing, -# "attributes_function": do_nothing, -# "type": "audiobookshelf_library" -# }, -# ) -# ) -# async_add_entities(entities, True) -# await asyncio.sleep(60) # Wait for 1 minute before refreshing again - async def async_setup_platform(hass: HomeAssistant, config, async_add_entities, discovery_info=None): """Set up the sensor platform.""" From 505933e86ce01421589a732cf38478864954dfca Mon Sep 17 00:00:00 2001 From: jadon <16850875+wolffshots@users.noreply.github.com> Date: Fri, 24 May 2024 19:43:39 +0200 Subject: [PATCH 12/20] chore: remove unused scan interval --- custom_components/audiobookshelf/sensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/audiobookshelf/sensor.py b/custom_components/audiobookshelf/sensor.py index c682bd8..7aa4b76 100644 --- a/custom_components/audiobookshelf/sensor.py +++ b/custom_components/audiobookshelf/sensor.py @@ -16,7 +16,6 @@ _LOGGER = logging.getLogger(__name__) DOMAIN = "audiobookshelf" -SCAN_INTERVAL = timedelta(seconds=10) async def count_active_users(data: dict) -> int: """ From 1830daf6aa42810832809c6f181176cc18537628 Mon Sep 17 00:00:00 2001 From: jadon <16850875+wolffshots@users.noreply.github.com> Date: Fri, 24 May 2024 19:46:22 +0200 Subject: [PATCH 13/20] chore: just making sure we don't truncate --- custom_components/audiobookshelf/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/audiobookshelf/sensor.py b/custom_components/audiobookshelf/sensor.py index 7aa4b76..23bc938 100644 --- a/custom_components/audiobookshelf/sensor.py +++ b/custom_components/audiobookshelf/sensor.py @@ -59,10 +59,10 @@ async def extract_library_details(data: dict) -> dict: def get_total_duration(total_duration: float): """Calculate the total duration in hours and round it to 0 decimal places.""" - return round(total_duration / 60 / 60, 0) + return round(total_duration / 60.0 / 60.0, 0) def get_total_size(total_size: float): - return round(total_size / 1024 / 1024 / 1024, 2) + return round(total_size / 1024.0 / 1024.0 / 1024.0, 2) async def fetch_library_stats(session, id): """Fetch data from a single endpoint.""" From 6fbb86686672bb4423ccc41c363b796f70f081f8 Mon Sep 17 00:00:00 2001 From: Jadon Wolffs <10365495-wolffs@users.noreply.gitlab.com> Date: Wed, 20 Nov 2024 11:20:24 +0000 Subject: [PATCH 14/20] chore: update from template manually --- .devcontainer.json | 41 + .gitattributes | 1 + .github/ISSUE_TEMPLATE/bug.yml | 55 + .github/ISSUE_TEMPLATE/config.yml | 1 + .github/ISSUE_TEMPLATE/feature_request.yml | 47 + .github/dependabot.yml | 15 + .ruff.toml | 26 + .vscode/tasks.json | 11 + CONTRIBUTING.md | 61 + config/configuration.yaml | 12 + custom_components/audiobookshelf/__init__.py | 11 +- hacs.json | 5 +- poetry.lock | 3289 ------------------ pyproject.toml | 80 - requirements.txt | 4 + scripts/develop | 20 + scripts/lint | 8 + scripts/setup | 7 + 18 files changed, 319 insertions(+), 3375 deletions(-) create mode 100644 .devcontainer.json create mode 100644 .gitattributes create mode 100644 .github/ISSUE_TEMPLATE/bug.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/dependabot.yml create mode 100644 .ruff.toml create mode 100644 .vscode/tasks.json create mode 100644 CONTRIBUTING.md create mode 100644 config/configuration.yaml delete mode 100644 poetry.lock delete mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100755 scripts/develop create mode 100755 scripts/lint create mode 100755 scripts/setup diff --git a/.devcontainer.json b/.devcontainer.json new file mode 100644 index 0000000..9a91ab6 --- /dev/null +++ b/.devcontainer.json @@ -0,0 +1,41 @@ +{ + "name": "ludeeus/integration_blueprint", + "image": "mcr.microsoft.com/devcontainers/python:3.12", + "postCreateCommand": "scripts/setup", + "forwardPorts": [ + 8123 + ], + "portsAttributes": { + "8123": { + "label": "Home Assistant", + "onAutoForward": "notify" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "charliermarsh.ruff", + "github.vscode-pull-request-github", + "ms-python.python", + "ms-python.vscode-pylance", + "ryanluker.vscode-coverage-gutters" + ], + "settings": { + "files.eol": "\n", + "editor.tabSize": 4, + "editor.formatOnPaste": true, + "editor.formatOnSave": true, + "editor.formatOnType": false, + "files.trimTrailingWhitespace": true, + "python.analysis.typeCheckingMode": "basic", + "python.analysis.autoImportCompletions": true, + "python.defaultInterpreterPath": "/usr/local/bin/python", + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff" + } + } + } + }, + "remoteUser": "vscode", + "features": {} +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..94f480d --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000..92fe7a5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,55 @@ +--- +name: "Bug report" +description: "Report a bug with the integration" +labels: "Bug" +body: +- type: markdown + attributes: + value: Before you open a new issue, search through the existing issues to see if others have had the same problem. +- type: textarea + attributes: + label: "System Health details" + description: "Paste the data from the System Health card in Home Assistant (https://www.home-assistant.io/more-info/system-health#github-issues)" + validations: + required: true +- type: checkboxes + attributes: + label: Checklist + options: + - label: I have enabled debug logging for my installation. + required: true + - label: I have filled out the issue template to the best of my ability. + required: true + - label: This issue only contains 1 issue (if you have multiple issues, open one issue for each issue). + required: true + - label: This issue is not a duplicate issue of any [previous issues](https://github.com/ludeeus/integration_blueprint/issues?q=is%3Aissue+label%3A%22Bug%22+).. + required: true +- type: textarea + attributes: + label: "Describe the issue" + description: "A clear and concise description of what the issue is." + validations: + required: true +- type: textarea + attributes: + label: Reproduction steps + description: "Without steps to reproduce, it will be hard to fix. It is very important that you fill out this part. Issues without it will be closed." + value: | + 1. + 2. + 3. + ... + validations: + required: true +- type: textarea + attributes: + label: "Debug logs" + description: "To enable debug logs check this https://www.home-assistant.io/integrations/logger/, this **needs** to include _everything_ from startup of Home Assistant to the point where you encounter the issue." + render: text + validations: + required: true + +- type: textarea + attributes: + label: "Diagnostics dump" + description: "Drag the diagnostics dump file here. (see https://www.home-assistant.io/integrations/diagnostics/ for info)" diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..ec4bb38 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..433467b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,47 @@ +--- +name: "Feature request" +description: "Suggest an idea for this project" +labels: "Feature+Request" +body: +- type: markdown + attributes: + value: Before you open a new feature request, search through the existing feature requests to see if others have had the same idea. +- type: checkboxes + attributes: + label: Checklist + options: + - label: I have filled out the template to the best of my ability. + required: true + - label: This only contains 1 feature request (if you have multiple feature requests, open one feature request for each feature request). + required: true + - label: This issue is not a duplicate feature request of [previous feature requests](https://github.com/ludeeus/integration_blueprint/issues?q=is%3Aissue+label%3A%22Feature+Request%22+). + required: true + +- type: textarea + attributes: + label: "Is your feature request related to a problem? Please describe." + description: "A clear and concise description of what the problem is." + placeholder: "I'm always frustrated when [...]" + validations: + required: true + +- type: textarea + attributes: + label: "Describe the solution you'd like" + description: "A clear and concise description of what you want to happen." + validations: + required: true + +- type: textarea + attributes: + label: "Describe alternatives you've considered" + description: "A clear and concise description of any alternative solutions or features you've considered." + validations: + required: true + +- type: textarea + attributes: + label: "Additional context" + description: "Add any other context or screenshots about the feature request here." + validations: + required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..04f2d40 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + ignore: + # Dependabot should not update Home Assistant as that should match the homeassistant key in hacs.json + - dependency-name: "homeassistant" \ No newline at end of file diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 0000000..8ea6a71 --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,26 @@ +# The contents of this file is based on https://github.com/home-assistant/core/blob/dev/pyproject.toml + +target-version = "py312" + +[lint] +select = [ + "ALL", +] + +ignore = [ + "ANN101", # Missing type annotation for `self` in method + "ANN401", # Dynamically typed expressions (typing.Any) are disallowed + "D203", # no-blank-line-before-class (incompatible with formatter) + "D212", # multi-line-summary-first-line (incompatible with formatter) + "COM812", # incompatible with formatter + "ISC001", # incompatible with formatter +] + +[lint.flake8-pytest-style] +fixture-parentheses = false + +[lint.pyupgrade] +keep-runtime-typing = true + +[lint.mccabe] +max-complexity = 25 \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..3aa1c50 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,11 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Run Home Assistant on port 8123", + "type": "shell", + "command": "scripts/develop", + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..61cb7c5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,61 @@ +# Contribution guidelines + +Contributing to this project should be as easy and transparent as possible, whether it's: + +- Reporting a bug +- Discussing the current state of the code +- Submitting a fix +- Proposing new features + +## Github is used for everything + +Github is used to host code, to track issues and feature requests, as well as accept pull requests. + +Pull requests are the best way to propose changes to the codebase. + +1. Fork the repo and create your branch from `main`. +2. If you've changed something, update the documentation. +3. Make sure your code lints (using `scripts/lint`). +4. Test you contribution. +5. Issue that pull request! + +## Any contributions you make will be under the MIT Software License + +In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern. + +## Report bugs using Github's [issues](../../issues) + +GitHub issues are used to track public bugs. +Report a bug by [opening a new issue](../../issues/new/choose); it's that easy! + +## Write bug reports with detail, background, and sample code + +**Great Bug Reports** tend to have: + +- A quick summary and/or background +- Steps to reproduce + - Be specific! + - Give sample code if you can. +- What you expected would happen +- What actually happens +- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) + +People _love_ thorough bug reports. I'm not even kidding. + +## Use a Consistent Coding Style + +Use [black](https://github.com/ambv/black) to make sure the code follows the style. + +## Test your code modification + +This custom component is based on [integration_blueprint template](https://github.com/ludeeus/integration_blueprint). + +It comes with development environment in a container, easy to launch +if you use Visual Studio Code. With this container you will have a stand alone +Home Assistant instance running and already configured with the included +[`configuration.yaml`](./config/configuration.yaml) +file. + +## License + +By contributing, you agree that your contributions will be licensed under its MIT License. diff --git a/config/configuration.yaml b/config/configuration.yaml new file mode 100644 index 0000000..ccc8410 --- /dev/null +++ b/config/configuration.yaml @@ -0,0 +1,12 @@ +# https://www.home-assistant.io/integrations/default_config/ +default_config: + +# https://www.home-assistant.io/integrations/homeassistant/ +homeassistant: + debug: true + +# https://www.home-assistant.io/integrations/logger/ +logger: + default: info + logs: + custom_components.integration_blueprint: debug diff --git a/custom_components/audiobookshelf/__init__.py b/custom_components/audiobookshelf/__init__.py index a60d330..e2f10bc 100644 --- a/custom_components/audiobookshelf/__init__.py +++ b/custom_components/audiobookshelf/__init__.py @@ -11,7 +11,7 @@ { vol.Required("api_key"): cv.string, vol.Required("api_url"): cv.string, - vol.Optional("scan_interval", default=300): cv.positive_int + vol.Optional("scan_interval", default=300): cv.positive_int, } ) }, @@ -20,6 +20,7 @@ _LOGGER = logging.getLogger(__name__) + async def async_setup(hass, config): """Set up the Audiobookshelf component.""" conf = config.get(DOMAIN) @@ -36,9 +37,11 @@ async def async_setup(hass, config): hass.data[DOMAIN] = { "api_key": api_key, "api_url": api_url, - "scan_interval": scan_interval + "scan_interval": scan_interval, } # Schedule the setup of sensor platform if needed - hass.async_create_task(discovery.async_load_platform(hass, "sensor", DOMAIN, {}, config)) + hass.async_create_task( + discovery.async_load_platform(hass, "sensor", DOMAIN, {}, config) + ) - return True \ No newline at end of file + return True diff --git a/hacs.json b/hacs.json index 5373809..af39f11 100644 --- a/hacs.json +++ b/hacs.json @@ -1,5 +1,6 @@ { "name": "Audiobookshelf", "hacs": "1.6.0", - "homeassistant": "0.118.0" -} + "homeassistant": "2024.11.0", + "render_readme": true +} \ No newline at end of file diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 2b734bf..0000000 --- a/poetry.lock +++ /dev/null @@ -1,3289 +0,0 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. - -[[package]] -name = "aiofiles" -version = "23.2.1" -description = "File support for asyncio." -optional = false -python-versions = ">=3.7" -files = [ - {file = "aiofiles-23.2.1-py3-none-any.whl", hash = "sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107"}, - {file = "aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a"}, -] - -[[package]] -name = "aiohttp" -version = "3.9.3" -description = "Async http client/server framework (asyncio)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"}, - {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"}, - {file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"}, - {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"}, - {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"}, - {file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"}, - {file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"}, - {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"}, - {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"}, - {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"}, - {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"}, - {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"}, - {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"}, - {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"}, - {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"}, - {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"}, - {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"}, - {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"}, - {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"}, - {file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"}, - {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"}, - {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"}, - {file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"}, - {file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"}, - {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"}, - {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"}, - {file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"}, - {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"}, - {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"}, - {file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"}, - {file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"}, - {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"}, -] - -[package.dependencies] -aiosignal = ">=1.1.2" -attrs = ">=17.3.0" -frozenlist = ">=1.1.1" -multidict = ">=4.5,<7.0" -yarl = ">=1.0,<2.0" - -[package.extras] -speedups = ["Brotli", "aiodns", "brotlicffi"] - -[[package]] -name = "aiohttp-cors" -version = "0.7.0" -description = "CORS support for aiohttp" -optional = false -python-versions = "*" -files = [ - {file = "aiohttp-cors-0.7.0.tar.gz", hash = "sha256:4d39c6d7100fd9764ed1caf8cebf0eb01bf5e3f24e2e073fda6234bc48b19f5d"}, - {file = "aiohttp_cors-0.7.0-py3-none-any.whl", hash = "sha256:0451ba59fdf6909d0e2cd21e4c0a43752bc0703d33fc78ae94d9d9321710193e"}, -] - -[package.dependencies] -aiohttp = ">=1.1" - -[[package]] -name = "aiohttp-fast-url-dispatcher" -version = "0.3.0" -description = "A faster URL dispatcher for aiohttp" -optional = false -python-versions = ">=3.8,<4.0" -files = [ - {file = "aiohttp_fast_url_dispatcher-0.3.0-py3-none-any.whl", hash = "sha256:e038458a34b79ef7c276b3257a1cdc73625da92cf4b64c2d0aefc4fe04dcdbbb"}, - {file = "aiohttp_fast_url_dispatcher-0.3.0.tar.gz", hash = "sha256:0fc11c60a4209429340d9d2d07b6b0819a45ebd0d47ceb78bea915dbe042addd"}, -] - -[package.dependencies] -aiohttp = ">=3.8.5,<3.10" - -[[package]] -name = "aiohttp-zlib-ng" -version = "0.3.1" -description = "Enable zlib_ng on aiohttp" -optional = false -python-versions = ">=3.8,<4.0" -files = [ - {file = "aiohttp_zlib_ng-0.3.1-py3-none-any.whl", hash = "sha256:aaf6de6ba3d6e0ec083adee45e437818965f19567973f51f0832721892ec3aaf"}, - {file = "aiohttp_zlib_ng-0.3.1.tar.gz", hash = "sha256:e8ac72b855a194da4c869e89f69a9dc9339229d2366931dfea34cff93fb960fa"}, -] - -[package.dependencies] -aiohttp = ">=3.8.5" -zlib-ng = ">=0.3.0" - -[package.extras] -isal = ["isal (>=1.5.3)"] - -[[package]] -name = "aiosignal" -version = "1.3.1" -description = "aiosignal: a list of registered asynchronous callbacks" -optional = false -python-versions = ">=3.7" -files = [ - {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, - {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, -] - -[package.dependencies] -frozenlist = ">=1.1.0" - -[[package]] -name = "anyio" -version = "4.2.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" -optional = false -python-versions = ">=3.8" -files = [ - {file = "anyio-4.2.0-py3-none-any.whl", hash = "sha256:745843b39e829e108e518c489b31dc757de7d2131d53fac32bd8df268227bfee"}, - {file = "anyio-4.2.0.tar.gz", hash = "sha256:e1875bb4b4e2de1669f4bc7869b6d3f54231cdced71605e6e64c9be77e3be50f"}, -] - -[package.dependencies] -idna = ">=2.8" -sniffio = ">=1.1" - -[package.extras] -doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] -trio = ["trio (>=0.23)"] - -[[package]] -name = "astral" -version = "2.2" -description = "Calculations for the position of the sun and moon." -optional = false -python-versions = ">=3.6" -files = [ - {file = "astral-2.2-py2.py3-none-any.whl", hash = "sha256:b9ef70faf32e81a8ba174d21e8f29dc0b53b409ef035f27e0749ddc13cb5982a"}, - {file = "astral-2.2.tar.gz", hash = "sha256:e41d9967d5c48be421346552f0f4dedad43ff39a83574f5ff2ad32b6627b6fbe"}, -] - -[package.dependencies] -pytz = "*" - -[[package]] -name = "async-interrupt" -version = "1.1.1" -description = "Context manager to raise an exception when a future is done" -optional = false -python-versions = ">=3.9,<4.0" -files = [ - {file = "async_interrupt-1.1.1-py3-none-any.whl", hash = "sha256:b0d8c8228b75834fd39608382e1c721d3e68e0011443e0f434b8e8e1cc7bd391"}, - {file = "async_interrupt-1.1.1.tar.gz", hash = "sha256:1e5999f0980b5db21293e4cd022518eeaf52284c0499631932a1df250cb99215"}, -] - -[[package]] -name = "asynctest" -version = "0.13.0" -description = "Enhance the standard unittest package with features for testing asyncio libraries" -optional = false -python-versions = ">=3.5" -files = [ - {file = "asynctest-0.13.0-py3-none-any.whl", hash = "sha256:5da6118a7e6d6b54d83a8f7197769d046922a44d2a99c21382f0a6e4fadae676"}, - {file = "asynctest-0.13.0.tar.gz", hash = "sha256:c27862842d15d83e6a34eb0b2866c323880eb3a75e4485b079ea11748fd77fac"}, -] - -[[package]] -name = "atomicwrites-homeassistant" -version = "1.4.1" -description = "Atomic file writes." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "atomicwrites-homeassistant-1.4.1.tar.gz", hash = "sha256:256a672106f16745445228d966240b77b55f46a096d20305901a57aa5d1f4c2f"}, - {file = "atomicwrites_homeassistant-1.4.1-py2.py3-none-any.whl", hash = "sha256:01457de800961db7d5b575f3c92e7fb56e435d88512c366afb0873f4f092bb0d"}, -] - -[[package]] -name = "attrs" -version = "23.2.0" -description = "Classes Without Boilerplate" -optional = false -python-versions = ">=3.7" -files = [ - {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, - {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, -] - -[package.extras] -cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] -dev = ["attrs[tests]", "pre-commit"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] -tests = ["attrs[tests-no-zope]", "zope-interface"] -tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] -tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] - -[[package]] -name = "awesomeversion" -version = "24.2.0" -description = "One version package to rule them all, One version package to find them, One version package to bring them all, and in the darkness bind them." -optional = false -python-versions = ">=3.8,<4.0" -files = [ - {file = "awesomeversion-24.2.0-py3-none-any.whl", hash = "sha256:ba365d1ad4e42aa5afdbc46b639f349e52db2d859d7c46a34744ca15ebf1430a"}, - {file = "awesomeversion-24.2.0.tar.gz", hash = "sha256:47a6dcbbe2921b725f75106a66ab30f26f1f33dbc5e07bc8e1e39d8eb921f53c"}, -] - -[[package]] -name = "bcrypt" -version = "4.1.2" -description = "Modern password hashing for your software and your servers" -optional = false -python-versions = ">=3.7" -files = [ - {file = "bcrypt-4.1.2-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ac621c093edb28200728a9cca214d7e838529e557027ef0581685909acd28b5e"}, - {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea505c97a5c465ab8c3ba75c0805a102ce526695cd6818c6de3b1a38f6f60da1"}, - {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57fa9442758da926ed33a91644649d3e340a71e2d0a5a8de064fb621fd5a3326"}, - {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb3bd3321517916696233b5e0c67fd7d6281f0ef48e66812db35fc963a422a1c"}, - {file = "bcrypt-4.1.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6cad43d8c63f34b26aef462b6f5e44fdcf9860b723d2453b5d391258c4c8e966"}, - {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:44290ccc827d3a24604f2c8bcd00d0da349e336e6503656cb8192133e27335e2"}, - {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:732b3920a08eacf12f93e6b04ea276c489f1c8fb49344f564cca2adb663b3e4c"}, - {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1c28973decf4e0e69cee78c68e30a523be441972c826703bb93099868a8ff5b5"}, - {file = "bcrypt-4.1.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b8df79979c5bae07f1db22dcc49cc5bccf08a0380ca5c6f391cbb5790355c0b0"}, - {file = "bcrypt-4.1.2-cp37-abi3-win32.whl", hash = "sha256:fbe188b878313d01b7718390f31528be4010fed1faa798c5a1d0469c9c48c369"}, - {file = "bcrypt-4.1.2-cp37-abi3-win_amd64.whl", hash = "sha256:9800ae5bd5077b13725e2e3934aa3c9c37e49d3ea3d06318010aa40f54c63551"}, - {file = "bcrypt-4.1.2-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:71b8be82bc46cedd61a9f4ccb6c1a493211d031415a34adde3669ee1b0afbb63"}, - {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e3c6642077b0c8092580c819c1684161262b2e30c4f45deb000c38947bf483"}, - {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:387e7e1af9a4dd636b9505a465032f2f5cb8e61ba1120e79a0e1cd0b512f3dfc"}, - {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f70d9c61f9c4ca7d57f3bfe88a5ccf62546ffbadf3681bb1e268d9d2e41c91a7"}, - {file = "bcrypt-4.1.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2a298db2a8ab20056120b45e86c00a0a5eb50ec4075b6142db35f593b97cb3fb"}, - {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:ba55e40de38a24e2d78d34c2d36d6e864f93e0d79d0b6ce915e4335aa81d01b1"}, - {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3566a88234e8de2ccae31968127b0ecccbb4cddb629da744165db72b58d88ca4"}, - {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b90e216dc36864ae7132cb151ffe95155a37a14e0de3a8f64b49655dd959ff9c"}, - {file = "bcrypt-4.1.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:69057b9fc5093ea1ab00dd24ede891f3e5e65bee040395fb1e66ee196f9c9b4a"}, - {file = "bcrypt-4.1.2-cp39-abi3-win32.whl", hash = "sha256:02d9ef8915f72dd6daaef40e0baeef8a017ce624369f09754baf32bb32dba25f"}, - {file = "bcrypt-4.1.2-cp39-abi3-win_amd64.whl", hash = "sha256:be3ab1071662f6065899fe08428e45c16aa36e28bc42921c4901a191fda6ee42"}, - {file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d75fc8cd0ba23f97bae88a6ec04e9e5351ff3c6ad06f38fe32ba50cbd0d11946"}, - {file = "bcrypt-4.1.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:a97e07e83e3262599434816f631cc4c7ca2aa8e9c072c1b1a7fec2ae809a1d2d"}, - {file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e51c42750b7585cee7892c2614be0d14107fad9581d1738d954a262556dd1aab"}, - {file = "bcrypt-4.1.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba4e4cc26610581a6329b3937e02d319f5ad4b85b074846bf4fef8a8cf51e7bb"}, - {file = "bcrypt-4.1.2.tar.gz", hash = "sha256:33313a1200a3ae90b75587ceac502b048b840fc69e7f7a0905b5f87fac7a1258"}, -] - -[package.extras] -tests = ["pytest (>=3.2.1,!=3.3.0)"] -typecheck = ["mypy"] - -[[package]] -name = "black" -version = "24.4.1" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-24.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f7749fd0d97ff9415975a1432fac7df89bf13c3833cea079e55fa004d5f28c0"}, - {file = "black-24.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:859f3cc5d2051adadf8fd504a01e02b0fd866d7549fff54bc9202d524d2e8bd7"}, - {file = "black-24.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59271c9c29dfa97f7fda51f56c7809b3f78e72fd8d2205189bbd23022a0618b6"}, - {file = "black-24.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:5ed9c34cba223149b5a0144951a0f33d65507cf82c5449cb3c35fe4b515fea9a"}, - {file = "black-24.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dae3ae59d6f2dc93700fd5034a3115434686e66fd6e63d4dcaa48d19880f2b0"}, - {file = "black-24.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5f8698974a81af83283eb47644f2711b5261138d6d9180c863fce673cbe04b13"}, - {file = "black-24.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f404b6e77043b23d0321fb7772522b876b6de737ad3cb97d6b156638d68ce81"}, - {file = "black-24.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:c94e52b766477bdcd010b872ba0714d5458536dc9d0734eff6583ba7266ffd89"}, - {file = "black-24.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:962d9e953872cdb83b97bb737ad47244ce2938054dc946685a4cad98520dab38"}, - {file = "black-24.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b1d8e3b2486b7dd522b1ab2ba1ec4907f0aa8f5e10a33c4271fb331d1d10b70c"}, - {file = "black-24.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed77e214b785148f57e43ca425b6e0850165144aa727d66ac604e56a70bb7825"}, - {file = "black-24.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:4ef4764437d7eba8386689cd06e1fb5341ee0ae2e9e22582b21178782de7ed94"}, - {file = "black-24.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:92b183f8eef5baf7b20a513abcf982ad616f544f593f6688bb2850d2982911f1"}, - {file = "black-24.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:945abd7b3572add997757c94295bb3e73c6ffaf3366b1f26cb2356a4bffd1dc3"}, - {file = "black-24.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db5154b9e5b478031371d8bc41ff37b33855fa223a6cfba456c9b73fb96f77d4"}, - {file = "black-24.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:afc84c33c1a9aaf3d73140cee776b4ddf73ff429ffe6b7c56dc1c9c10725856d"}, - {file = "black-24.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0889f4eb8b3bdf8b189e41a71cf0dbb8141a98346cd1a2695dea5995d416e940"}, - {file = "black-24.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5bb0143f175db45a55227eefd63e90849d96c266330ba31719e9667d0d5ec3b9"}, - {file = "black-24.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:713a04a78e78f28ef7e8df7a16fe075670ea164860fcef3885e4f3dffc0184b3"}, - {file = "black-24.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:171959bc879637a8cdbc53dc3fddae2a83e151937a28cf605fd175ce61e0e94a"}, - {file = "black-24.4.1-py3-none-any.whl", hash = "sha256:ecbab810604fe02c70b3a08afd39beb599f7cc9afd13e81f5336014133b4fe35"}, - {file = "black-24.4.1.tar.gz", hash = "sha256:5241612dc8cad5b6fd47432b8bd04db80e07cfbc53bb69e9ae18985063bcb8dd"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "bleak" -version = "0.21.1" -description = "Bluetooth Low Energy platform Agnostic Klient" -optional = false -python-versions = ">=3.8,<3.13" -files = [ - {file = "bleak-0.21.1-py3-none-any.whl", hash = "sha256:ccec260a0f5ec02dd133d68b0351c0151b2ecf3ddd0bcabc4c04a1cdd7f33256"}, - {file = "bleak-0.21.1.tar.gz", hash = "sha256:ec4a1a2772fb315b992cbaa1153070c7e26968a52b0e2727035f443a1af5c18f"}, -] - -[package.dependencies] -bleak-winrt = {version = ">=1.2.0,<2.0.0", markers = "platform_system == \"Windows\" and python_version < \"3.12\""} -dbus-fast = {version = ">=1.83.0,<3", markers = "platform_system == \"Linux\""} -pyobjc-core = {version = ">=9.2,<10.0", markers = "platform_system == \"Darwin\""} -pyobjc-framework-CoreBluetooth = {version = ">=9.2,<10.0", markers = "platform_system == \"Darwin\""} -pyobjc-framework-libdispatch = {version = ">=9.2,<10.0", markers = "platform_system == \"Darwin\""} -typing-extensions = {version = ">=4.7.0", markers = "python_version < \"3.12\""} -"winrt-Windows.Devices.Bluetooth" = {version = "2.0.0b1", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""} -"winrt-Windows.Devices.Bluetooth.Advertisement" = {version = "2.0.0b1", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""} -"winrt-Windows.Devices.Bluetooth.GenericAttributeProfile" = {version = "2.0.0b1", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""} -"winrt-Windows.Devices.Enumeration" = {version = "2.0.0b1", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""} -"winrt-Windows.Foundation" = {version = "2.0.0b1", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""} -"winrt-Windows.Foundation.Collections" = {version = "2.0.0b1", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""} -"winrt-Windows.Storage.Streams" = {version = "2.0.0b1", markers = "platform_system == \"Windows\" and python_version >= \"3.12\""} - -[[package]] -name = "bleak-retry-connector" -version = "3.4.0" -description = "A connector for Bleak Clients that handles transient connection failures" -optional = false -python-versions = ">=3.10,<3.13" -files = [ - {file = "bleak_retry_connector-3.4.0-py3-none-any.whl", hash = "sha256:5f6c26efa03d3ae94c55045b99079fc27f1a9480cf4c8f6d6f027a36bf15cf66"}, - {file = "bleak_retry_connector-3.4.0.tar.gz", hash = "sha256:71f30928180b74f0381e0752f681d18d8de888faa9c81c78cd17123718909ea0"}, -] - -[package.dependencies] -bleak = ">=0.21.0" -bluetooth-adapters = {version = ">=0.15.2", markers = "platform_system == \"Linux\""} -dbus-fast = {version = ">=1.14.0", markers = "platform_system == \"Linux\""} - -[[package]] -name = "bleak-winrt" -version = "1.2.0" -description = "Python WinRT bindings for Bleak" -optional = false -python-versions = "*" -files = [ - {file = "bleak-winrt-1.2.0.tar.gz", hash = "sha256:0577d070251b9354fc6c45ffac57e39341ebb08ead014b1bdbd43e211d2ce1d6"}, - {file = "bleak_winrt-1.2.0-cp310-cp310-win32.whl", hash = "sha256:a2ae3054d6843ae0cfd3b94c83293a1dfd5804393977dd69bde91cb5099fc47c"}, - {file = "bleak_winrt-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:677df51dc825c6657b3ae94f00bd09b8ab88422b40d6a7bdbf7972a63bc44e9a"}, - {file = "bleak_winrt-1.2.0-cp311-cp311-win32.whl", hash = "sha256:9449cdb942f22c9892bc1ada99e2ccce9bea8a8af1493e81fefb6de2cb3a7b80"}, - {file = "bleak_winrt-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:98c1b5a6a6c431ac7f76aa4285b752fe14a1c626bd8a1dfa56f66173ff120bee"}, - {file = "bleak_winrt-1.2.0-cp37-cp37m-win32.whl", hash = "sha256:623ac511696e1f58d83cb9c431e32f613395f2199b3db7f125a3d872cab968a4"}, - {file = "bleak_winrt-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:13ab06dec55469cf51a2c187be7b630a7a2922e1ea9ac1998135974a7239b1e3"}, - {file = "bleak_winrt-1.2.0-cp38-cp38-win32.whl", hash = "sha256:5a36ff8cd53068c01a795a75d2c13054ddc5f99ce6de62c1a97cd343fc4d0727"}, - {file = "bleak_winrt-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:810c00726653a962256b7acd8edf81ab9e4a3c66e936a342ce4aec7dbd3a7263"}, - {file = "bleak_winrt-1.2.0-cp39-cp39-win32.whl", hash = "sha256:dd740047a08925bde54bec357391fcee595d7b8ca0c74c87170a5cbc3f97aa0a"}, - {file = "bleak_winrt-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:63130c11acfe75c504a79c01f9919e87f009f5e742bfc7b7a5c2a9c72bf591a7"}, -] - -[[package]] -name = "bluetooth-adapters" -version = "0.17.0" -description = "Tools to enumerate and find Bluetooth Adapters" -optional = false -python-versions = ">=3.9,<3.13" -files = [ - {file = "bluetooth_adapters-0.17.0-py3-none-any.whl", hash = "sha256:24a8d485e77289f037494fe52877b5c20051feb4747f0173b768a66d8e2b13c4"}, - {file = "bluetooth_adapters-0.17.0.tar.gz", hash = "sha256:6a82ec713a4a5eccb870d7e9ff98e4002bbae885e1ab0f98f5056fc68db22325"}, -] - -[package.dependencies] -aiohttp = ">=3.8.1" -bleak = ">=0.21.1" -dbus-fast = ">=1.21.0" -mac-vendor-lookup = ">=0.1.12" -usb-devices = ">=0.4.5" - -[package.extras] -docs = ["Sphinx (>=5,<8)", "myst-parser (>=0.18,<2.1)", "sphinx-rtd-theme (>=1,<3)"] - -[[package]] -name = "bluetooth-auto-recovery" -version = "1.2.3" -description = "Recover bluetooth adapters that are in an stuck state" -optional = false -python-versions = ">=3.9,<4.0" -files = [ - {file = "bluetooth_auto_recovery-1.2.3-py3-none-any.whl", hash = "sha256:a131166f2486e484b5257371cd416f2bf1c6b32a897db0ad29fe11c1eb2a40f5"}, - {file = "bluetooth_auto_recovery-1.2.3.tar.gz", hash = "sha256:1040aa81e31398dba971028c8663ba2555b28e4396efe56dee2f199977c3057b"}, -] - -[package.dependencies] -bluetooth-adapters = ">=0.16.0" -btsocket = ">=0.2.0" -PyRIC = ">=0.1.6.3" -usb-devices = ">=0.4.1" - -[package.extras] -docs = ["Sphinx (>=5.0,<6.0)", "myst-parser (>=0.18,<0.19)", "sphinx-rtd-theme (>=1.0,<2.0)"] - -[[package]] -name = "bluetooth-data-tools" -version = "1.19.0" -description = "Tools for converting bluetooth data and packets" -optional = false -python-versions = ">=3.10,<4.0" -files = [ - {file = "bluetooth_data_tools-1.19.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:cc9130b92fe5a11ba91b4632cb5b5c5bdf9dfe8c4741f610ae277c971c226a11"}, - {file = "bluetooth_data_tools-1.19.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c0137966b6a1e3b95b50fa81b64c53d87ab41ab53d9aab69bad4113c7311afc"}, - {file = "bluetooth_data_tools-1.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98115130a41122360b799aa7b3492ccea45bb5095ac16bb7b727111737ea5ce"}, - {file = "bluetooth_data_tools-1.19.0-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:a3de1c93f210bcae0048fa0ebc96b097613eddcbea5b05824df8b8fc90d343f8"}, - {file = "bluetooth_data_tools-1.19.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1b57bfbb878decfda85816b0744d6a18c82a353f6531ef5b5037a4088865c7de"}, - {file = "bluetooth_data_tools-1.19.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:252fa800bfc22de0cc19e491e99e155305a47788cff6c31d56450f6137c74f6b"}, - {file = "bluetooth_data_tools-1.19.0-cp310-cp310-win32.whl", hash = "sha256:541adf9b8a093c4333f7c4b99e2662ec7c38a9dde8b2561d5e280da06573707a"}, - {file = "bluetooth_data_tools-1.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:74843706ad65dbc0c32cf4ce073e266353f48970ab0a6ae2edda1969fbb318d1"}, - {file = "bluetooth_data_tools-1.19.0-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:2e36ec53e6264faa9a75e8b704ce8f274ab0ba336ce3e5b7a279c254b2231bc2"}, - {file = "bluetooth_data_tools-1.19.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ebd2783c1f487d2c991f4f2ed399ea452fdee605e4de4417f353a5bee494b82"}, - {file = "bluetooth_data_tools-1.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:238db5bf2f19872c1318fcc35a4efc48961065ff7c227a0d239b220de1576d80"}, - {file = "bluetooth_data_tools-1.19.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bae1f9a2a1a89c5268c796d32c6a000d783ad9fc73fa9a271a287c6eaa1ecd97"}, - {file = "bluetooth_data_tools-1.19.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9763c3d3df14d16884775d61f034499afa4a32b8538cb6f2c71630ef6967295b"}, - {file = "bluetooth_data_tools-1.19.0-cp311-cp311-win32.whl", hash = "sha256:d23997cc3d849bfdf85ef42645b999054e4913a320ad10e77ca7557806008768"}, - {file = "bluetooth_data_tools-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:4b73b3509497d642404c8efc2b42de3b34d06172a88dbeacc6f8ebc1de1e550f"}, - {file = "bluetooth_data_tools-1.19.0-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:8371aeb50cef225a17df9a013277b3363ca59a53097cec5cb1ec89dea8323c3c"}, - {file = "bluetooth_data_tools-1.19.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:586bdeca2e24a41ceddad7fa02a3ed7a9c865f13ee95e8231b6a56c0a07e3e87"}, - {file = "bluetooth_data_tools-1.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56f6f822f2d69a620151f2a0bb9ad8502e5c59f84cff473befe545709bd952b5"}, - {file = "bluetooth_data_tools-1.19.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8c6f4c95590c1524bf1cb3e543ee48760965b9eb5a2944b90e82f42200fce759"}, - {file = "bluetooth_data_tools-1.19.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:72188dd239728d6845d759671733d6b4cbf37face2adfd844fdcf431fce4103f"}, - {file = "bluetooth_data_tools-1.19.0-cp312-cp312-win32.whl", hash = "sha256:30acd97edc15d97eacae205b2c495371d7ae8cd878b3d72424286935ebd7d857"}, - {file = "bluetooth_data_tools-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:ce08ad5d2ce7ea624388bf5263c2688c356fae45c583d7331439b489f4191f01"}, - {file = "bluetooth_data_tools-1.19.0-pp310-pypy310_pp73-macosx_11_0_x86_64.whl", hash = "sha256:8e81410105a4b753fb4afc6a881fa666c0dcba70fb38fe26835f4bf8baa71cd4"}, - {file = "bluetooth_data_tools-1.19.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c183ed080682cd4ab9f84205d648c46b9d65d41f4d9bbd65dc3a4787c07e633c"}, - {file = "bluetooth_data_tools-1.19.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e6dde7df8f61f13bb2e6bff29a93a190f31a858d753c48986c66c9aa3bdd3c2"}, - {file = "bluetooth_data_tools-1.19.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:6e5d1546c3a2d400d78c70ededc49c4f7b418386b4b1f68611871ec3a39a8c85"}, - {file = "bluetooth_data_tools-1.19.0.tar.gz", hash = "sha256:5b58349126efbb38d61222267cf85bf1766f832cc5744a59e70a1a2653e477d9"}, -] - -[package.dependencies] -cryptography = ">=41.0.3" - -[package.extras] -docs = ["Sphinx (>=5.0,<6.0)", "myst-parser (>=0.18,<0.19)", "sphinx-rtd-theme (>=1.0,<2.0)"] - -[[package]] -name = "btsocket" -version = "0.2.0" -description = "Python library for BlueZ Bluetooth Management API" -optional = false -python-versions = "*" -files = [ - {file = "btsocket-0.2.0-py2.py3-none-any.whl", hash = "sha256:0d33893039284fa3a496dd31cb15227e1cf07f0d42d3843d3284b522cccf575a"}, - {file = "btsocket-0.2.0.tar.gz", hash = "sha256:6862250b7e0973d2beea9e49d3b5ced324d80e5003a0350856879e537ba777a8"}, -] - -[package.extras] -dev = ["bumpversion", "coverage", "pycodestyle", "pygments", "sphinx", "sphinx-rtd-theme", "twine"] -docs = ["pygments", "sphinx", "sphinx-rtd-theme"] -rel = ["bumpversion", "twine"] -test = ["coverage", "pycodestyle"] - -[[package]] -name = "certifi" -version = "2023.11.17" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, -] - -[[package]] -name = "cffi" -version = "1.16.0" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.8" -files = [ - {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, - {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, - {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, - {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, - {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, - {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, - {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, - {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, - {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, - {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, - {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, - {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, - {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, - {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, - {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, -] - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "cfgv" -version = "3.4.0" -description = "Validate configuration and produce human readable error messages." -optional = false -python-versions = ">=3.8" -files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - -[[package]] -name = "ciso8601" -version = "2.3.1" -description = "Fast ISO8601 date time parser for Python written in C" -optional = false -python-versions = "*" -files = [ - {file = "ciso8601-2.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:57db9a28e87f9e4fccba643fb70a9ba1515adc5e1325508eb2c10dd96620314c"}, - {file = "ciso8601-2.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8c59646197ddbf84909b6c31d55f744cfeef51811e3910b61d0f58f2885823fd"}, - {file = "ciso8601-2.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6a25da209193134842cd573464a5323f46fcc3ed781b633f15a34793ba7e1064"}, - {file = "ciso8601-2.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3ae83f4e60fc7e260a4188e4ec4ac1bdd40bdb382eeda92fc266c5aa2f0a1ee"}, - {file = "ciso8601-2.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2c1ef17d1ea52a39b2dce6535583631ae4bfb65c76f0ee8c99413a6861a46c9e"}, - {file = "ciso8601-2.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3771049ba29bd1077588c0a24be1d53f7493e7cc686b2caa92f7cae129636a0e"}, - {file = "ciso8601-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:55381365366dacb57207cec610d26c9a6c0d237cb65a0cf67a2baaa5299f2366"}, - {file = "ciso8601-2.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f25647803c9a5aaaed130c53bbec7ea06a4f95ba5c7016f59e444b4ef7ac39e"}, - {file = "ciso8601-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:473288cd63efe6a2cf3f4b5f90394e53095358ccb13d6128f87a2da85d0f389b"}, - {file = "ciso8601-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:121d27c55f4455eaa27ba3bd602beca915df9a352f235e935636a4660321070e"}, - {file = "ciso8601-2.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef44cb4dc83f37019a356c7a72692cbe17072456f4879ca6bc0339f67eee5d00"}, - {file = "ciso8601-2.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:364702e338212b6c1a8643d9399ada21560cf132f363853473560625cb4207f1"}, - {file = "ciso8601-2.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8acb45545e6a654310c6ef788aacb2d73686646c414ceacdd9f5f78a83165af5"}, - {file = "ciso8601-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:99addd8b113f85fac549167073f317a318cd2b5841552598ceb97b97c5708a38"}, - {file = "ciso8601-2.3.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f39bb5936debf21c52e5d52b89f26857c303da80c43a72883946096a6ef5e561"}, - {file = "ciso8601-2.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:21cf83ca945bb26ecd95364ae2c9ed0276378e5fe35ce1b64d4c6d5b33038ea3"}, - {file = "ciso8601-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:013410263cba46748d2de29e9894341ae41223356cde7970478c32bd0984d10c"}, - {file = "ciso8601-2.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b26935687ef1837b56997d8c61f1d789e698be58b261410e629eda9c89812141"}, - {file = "ciso8601-2.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0d980a2a88030d4d8b2434623c250866a75b4979d289eba69bec445c51ace99f"}, - {file = "ciso8601-2.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:87721de54e008fb1c4c3978553b05a9c417aa25b76ddf5702d6f7e8d9b109288"}, - {file = "ciso8601-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f107a4c051e7c0416824279264d94f4ed3da0fbd82bd96ec3c3293426826de4"}, - {file = "ciso8601-2.3.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:02ecbd7c8336c4e1c6bb725b898e29414ee92bdc0be6c72fb07036836b1ac867"}, - {file = "ciso8601-2.3.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36525b1f380f4601533f4631c69911e44efb9cb50beab1da3248b0daa32bced4"}, - {file = "ciso8601-2.3.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:874d20c6339e9096baaadfd1b9610bb8d5b373a0f2858cc06de8142b98d2129c"}, - {file = "ciso8601-2.3.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:46a3663c2cf838f0149e1cdb8e4bdc95716e03cf2d5f803a6eb755d825896ebe"}, - {file = "ciso8601-2.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e8e76825f80ce313d75bbbef1d3b8bd9e0ce31dbc157d1981e9593922c9983e7"}, - {file = "ciso8601-2.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6850889813f3135e0aa18f0aaec64249dd81d36a1b9bce60bb45182930c86663"}, - {file = "ciso8601-2.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c690ac24ec3407f68cdfd5e032c6cb18126ef33d6c4b3db0669b9cbb8c96bd4"}, - {file = "ciso8601-2.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:024c52d5d0670f15ca3dc53eff7345b6eaee22fba929675f6a408f9d1e159d98"}, - {file = "ciso8601-2.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7ae2c3442d042de5330672d0d28486ed92f9d7c6dc010943aa618fd361d4638"}, - {file = "ciso8601-2.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:22128f0def36fa3c4cf0c482a216e8b8ad722def08bc11c07438eff82bdcd02a"}, - {file = "ciso8601-2.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:025859ec286a994aa3f2120c0f27d053b719cabc975398338374f2cc1f961125"}, - {file = "ciso8601-2.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2a64ff58904d4418d60fa9619014ae820ae21f7aef58da46df78a4c647f951ec"}, - {file = "ciso8601-2.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d1f85c0b7fa742bbfd18177137ccbaa3f867dd06157f91595075bb959a733048"}, - {file = "ciso8601-2.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ac59453664781dfddebee51f9a36e41819993823fdb09ddc0ce0e4bd3ff0c3"}, - {file = "ciso8601-2.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:eaecca7e0c3ef9e8f5e963e212b083684e849f9a9bb25834d3042363223a73cd"}, - {file = "ciso8601-2.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ad8f417c45eea973a694599b96f40d841215bfee352cb9963383e8d66b309981"}, - {file = "ciso8601-2.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:b869396e9756a7c0696d8eb69ce1d8980bea5e25c86e5996b10d78c900a4362c"}, - {file = "ciso8601-2.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7eb7b5ef8714d3d1fe9f3256b7a679ad783da899a0b7503a5ace78186735f840"}, - {file = "ciso8601-2.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:02828107880848ff497971ebc98e6dc851ad7af8ec14a58089e0e11f3111cad6"}, - {file = "ciso8601-2.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:566b4a8b2f9717e54ffcdd732a7c8051a91da30a60a4f1dafb62e303a1dbac69"}, - {file = "ciso8601-2.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58a749d63f28c2eda71416c9d6014113b0748abf5fd14c502b01bd515502fedf"}, - {file = "ciso8601-2.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:cb135de0e3b8feb7e74a4f7a234e8c8545957fe8d26316a1a549553f425c629d"}, - {file = "ciso8601-2.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:695583810836a42945084b33621b22b0309701c6916689f6a3588fa44c5bc413"}, - {file = "ciso8601-2.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:21204d98496cf5c0511dc21533be55c2a2d34b8c65603946a116812ffbae3b2d"}, - {file = "ciso8601-2.3.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c29ea2b03dee2dc0a5d3e4a0b7d7768c597781e9fa451fe1025600f7cb55a89"}, - {file = "ciso8601-2.3.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7533256af90724b8b7a707dcd1be4b67989447595c8e1e1c28399d4fd51dac50"}, - {file = "ciso8601-2.3.1-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4bc9d577c0d1e57532513fc2899f5231727e28981a426767f7fa13dacb18c06"}, - {file = "ciso8601-2.3.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:4e30501eed43eea7ef64f032c81cd1d8b2020035cbdcefad40db72e2f3bc97ff"}, - {file = "ciso8601-2.3.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:070f568de3bc269268296cb9265704dc5fcb9d4c12b1f1c67536624174df5d09"}, - {file = "ciso8601-2.3.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:9065053c034c80c0afd74c71a4906675d07078a05cfd1cb5ff70661378cdbe60"}, - {file = "ciso8601-2.3.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac00d293cdb3d1a5c78e09b3d75c7b0292ab45d5b26853b436ff5087eba2165"}, - {file = "ciso8601-2.3.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:06941e2ee46701f083aeb21d13eb762d74d5ed6c46ff22119f27a42ed6edc8f9"}, - {file = "ciso8601-2.3.1.tar.gz", hash = "sha256:3212c7ffe5d8080270548b5f2692ffd2039683b6628a8d2ad456122cc5793c4c"}, -] - -[[package]] -name = "click" -version = "8.1.7" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "coverage" -version = "7.4.3" -description = "Code coverage measurement for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "coverage-7.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8580b827d4746d47294c0e0b92854c85a92c2227927433998f0d3320ae8a71b6"}, - {file = "coverage-7.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:718187eeb9849fc6cc23e0d9b092bc2348821c5e1a901c9f8975df0bc785bfd4"}, - {file = "coverage-7.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:767b35c3a246bcb55b8044fd3a43b8cd553dd1f9f2c1eeb87a302b1f8daa0524"}, - {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae7f19afe0cce50039e2c782bff379c7e347cba335429678450b8fe81c4ef96d"}, - {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba3a8aaed13770e970b3df46980cb068d1c24af1a1968b7818b69af8c4347efb"}, - {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ee866acc0861caebb4f2ab79f0b94dbfbdbfadc19f82e6e9c93930f74e11d7a0"}, - {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:506edb1dd49e13a2d4cac6a5173317b82a23c9d6e8df63efb4f0380de0fbccbc"}, - {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd6545d97c98a192c5ac995d21c894b581f1fd14cf389be90724d21808b657e2"}, - {file = "coverage-7.4.3-cp310-cp310-win32.whl", hash = "sha256:f6a09b360d67e589236a44f0c39218a8efba2593b6abdccc300a8862cffc2f94"}, - {file = "coverage-7.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:18d90523ce7553dd0b7e23cbb28865db23cddfd683a38fb224115f7826de78d0"}, - {file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"}, - {file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"}, - {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"}, - {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"}, - {file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"}, - {file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"}, - {file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"}, - {file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"}, - {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"}, - {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"}, - {file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"}, - {file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"}, - {file = "coverage-7.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28ca2098939eabab044ad68850aac8f8db6bf0b29bc7f2887d05889b17346454"}, - {file = "coverage-7.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:280459f0a03cecbe8800786cdc23067a8fc64c0bd51dc614008d9c36e1659d7e"}, - {file = "coverage-7.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c0cdedd3500e0511eac1517bf560149764b7d8e65cb800d8bf1c63ebf39edd2"}, - {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a9babb9466fe1da12417a4aed923e90124a534736de6201794a3aea9d98484e"}, - {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dec9de46a33cf2dd87a5254af095a409ea3bf952d85ad339751e7de6d962cde6"}, - {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:16bae383a9cc5abab9bb05c10a3e5a52e0a788325dc9ba8499e821885928968c"}, - {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2c854ce44e1ee31bda4e318af1dbcfc929026d12c5ed030095ad98197eeeaed0"}, - {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ce8c50520f57ec57aa21a63ea4f325c7b657386b3f02ccaedeccf9ebe27686e1"}, - {file = "coverage-7.4.3-cp38-cp38-win32.whl", hash = "sha256:708a3369dcf055c00ddeeaa2b20f0dd1ce664eeabde6623e516c5228b753654f"}, - {file = "coverage-7.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:1bf25fbca0c8d121a3e92a2a0555c7e5bc981aee5c3fdaf4bb7809f410f696b9"}, - {file = "coverage-7.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b253094dbe1b431d3a4ac2f053b6d7ede2664ac559705a704f621742e034f1f"}, - {file = "coverage-7.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77fbfc5720cceac9c200054b9fab50cb2a7d79660609200ab83f5db96162d20c"}, - {file = "coverage-7.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6679060424faa9c11808598504c3ab472de4531c571ab2befa32f4971835788e"}, - {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4af154d617c875b52651dd8dd17a31270c495082f3d55f6128e7629658d63765"}, - {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8640f1fde5e1b8e3439fe482cdc2b0bb6c329f4bb161927c28d2e8879c6029ee"}, - {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:69b9f6f66c0af29642e73a520b6fed25ff9fd69a25975ebe6acb297234eda501"}, - {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0842571634f39016a6c03e9d4aba502be652a6e4455fadb73cd3a3a49173e38f"}, - {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a78ed23b08e8ab524551f52953a8a05d61c3a760781762aac49f8de6eede8c45"}, - {file = "coverage-7.4.3-cp39-cp39-win32.whl", hash = "sha256:c0524de3ff096e15fcbfe8f056fdb4ea0bf497d584454f344d59fce069d3e6e9"}, - {file = "coverage-7.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0209a6369ccce576b43bb227dc8322d8ef9e323d089c6f3f26a597b09cb4d2aa"}, - {file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"}, - {file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"}, -] - -[package.extras] -toml = ["tomli"] - -[[package]] -name = "croniter" -version = "1.4.1" -description = "croniter provides iteration for datetime object with cron like format" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "croniter-1.4.1-py2.py3-none-any.whl", hash = "sha256:9595da48af37ea06ec3a9f899738f1b2c1c13da3c38cea606ef7cd03ea421128"}, - {file = "croniter-1.4.1.tar.gz", hash = "sha256:1a6df60eacec3b7a0aa52a8f2ef251ae3dd2a7c7c8b9874e73e791636d55a361"}, -] - -[package.dependencies] -python-dateutil = "*" - -[[package]] -name = "cryptography" -version = "42.0.5" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -optional = false -python-versions = ">=3.7" -files = [ - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, - {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"}, - {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"}, - {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"}, - {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"}, - {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"}, - {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"}, - {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"}, - {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"}, - {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"}, - {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"}, - {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"}, - {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"}, - {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"}, -] - -[package.dependencies] -cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} - -[package.extras] -docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] -docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] -nox = ["nox"] -pep8test = ["check-sdist", "click", "mypy", "ruff"] -sdist = ["build"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] -test-randomorder = ["pytest-randomly"] - -[[package]] -name = "dbus-fast" -version = "2.21.0" -description = "A faster version of dbus-next" -optional = false -python-versions = ">=3.7,<4.0" -files = [ - {file = "dbus_fast-2.21.0-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:67fc9ca7c8fa6dec14950f3f785eab78256cb1533c0a2a6237e8dfec9dfb03a5"}, - {file = "dbus_fast-2.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45e9296254a8e6ba827c6ba77d9967111c9bd010525c341aede9eb4a3eb9baef"}, - {file = "dbus_fast-2.21.0-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:6a46b180102be81c2146d7b924444cb414cb742e0944133ed0c1f90ebe64c1cb"}, - {file = "dbus_fast-2.21.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4b9f40e28a12b0322eb7d32c1405e5da29a109fd51628f56e8897cf85127b6d5"}, - {file = "dbus_fast-2.21.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fb0bb066f4c449830480cc0a20b6ccb4f2581d06876b2150b328cee184ff33ee"}, - {file = "dbus_fast-2.21.0-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:cb47669f8bd14d2d55d853b3ce501f36981a4fb088a8168adc91b19b9110a05a"}, - {file = "dbus_fast-2.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ef1474209830eabe482f52be85af4d38af83bb021a6fe24a28450c091af5a93"}, - {file = "dbus_fast-2.21.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c4e623fdd921430c02c584fba922d2d67c76d5afd97013072ea9623d4fd44613"}, - {file = "dbus_fast-2.21.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b4bbd7d1deec9ecb5ef8bc5e5e9fcb87f0f8d6f3157e7e3318c54f8b013e18be"}, - {file = "dbus_fast-2.21.0-cp312-cp312-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:88f39f55e2cda6564c4fb9759aeb8354434e0d5778679f2654dfe489e3b597a4"}, - {file = "dbus_fast-2.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e429c8c416afad4321b9717b71e120bc19258d62d13f9b0a392f24136fb1903"}, - {file = "dbus_fast-2.21.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:63fff86527b4f9881d4a501ab613e497c72a4a97929ac2dd777cd26ebc1379fa"}, - {file = "dbus_fast-2.21.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6c4a2b1340b59d67813b06a6b91e9092c32b81fbefbc0d38c3bcdeabac4bc902"}, - {file = "dbus_fast-2.21.0-cp37-cp37m-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:5a264d8ab81cbedb0235dc4b8dd1a5b7df44b07c42a1b0a4198fd6fec1d5dda2"}, - {file = "dbus_fast-2.21.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5a6e59927aaef2f55628ba177561c164859a36388dfc7a4c9c9d76c378efcb6"}, - {file = "dbus_fast-2.21.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bf253e1a421ef395a43903e340d83ee9657e33ad694a5b45c9b438aa84ccc38a"}, - {file = "dbus_fast-2.21.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:23db07bae0cdd4269f40432e53b7bd82a2cc4d287be88d265f50e53f944ed3cc"}, - {file = "dbus_fast-2.21.0-cp38-cp38-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:18a10b49d208a3ea68f3a62d4d82e42638cc19521a04b5ffd3a6554f41c3e043"}, - {file = "dbus_fast-2.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:240d604578321f7b949b436f0f7f6097617c3a9ff70a2ce28378f5c2d9501571"}, - {file = "dbus_fast-2.21.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:921188573afadbefb37503768e2b7b3e381c648358e9d9413a7466fb6fb1d4a8"}, - {file = "dbus_fast-2.21.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:01ea44dad016e684e27615cd8ea9afd5ddfb0409d031f8ab85d540a5f78722e9"}, - {file = "dbus_fast-2.21.0-cp39-cp39-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:fabbb50bcda006b1e48d4a343ee96050c43fcbc30dd9856080378ef2b3465870"}, - {file = "dbus_fast-2.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:edddabd57733b4a6c5348ac2e58b662d042df9070ebac867753a93d6b5589e2a"}, - {file = "dbus_fast-2.21.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fed1a8cd5d11416a8bcce9e588a4372d6d62c76dec275798d07095d699671885"}, - {file = "dbus_fast-2.21.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:08d490288daa283de619eaac34c6863d9bd7ecea5a5a59b02d7a59d7214a3b59"}, - {file = "dbus_fast-2.21.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:ad4aa825c58458fe333fc5e41cfd29c22947c1c30d500bccc2e39fc15787dea0"}, - {file = "dbus_fast-2.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9e91c0db2e2452f9cf6c80b94edfddefd01b9b89fe8621f58296b8c29986997"}, - {file = "dbus_fast-2.21.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:63affae84c36884266cb63fde0e148d4aeab26d76243e4496f3f3568f17e23d7"}, - {file = "dbus_fast-2.21.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3bb6d7c91e35fbb5e9fb24acdceb512de31ab5a752e0818a683394e852b9654"}, - {file = "dbus_fast-2.21.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:b0bd86710736d2ca438987a64e9627710886c75ff7aacb590181fa896a9102e4"}, - {file = "dbus_fast-2.21.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19dbd4b4834947f2b78d5d29ca934e0cc2a98a4c256aa071103168f0805676ea"}, - {file = "dbus_fast-2.21.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:e46e1b5e98318c4f95e1f3ea041eec646979c4da069167c2b6f4cd3e54d027cc"}, - {file = "dbus_fast-2.21.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d87ce29c43b92123969ef9c42d7b599d315d9973ed8d69acfafc655d41c40faf"}, - {file = "dbus_fast-2.21.0.tar.gz", hash = "sha256:f582f6f16791ced6067dab325fae444edf7ce0704315b90c2a473090636a6fe0"}, -] - -[[package]] -name = "distlib" -version = "0.3.8" -description = "Distribution utilities" -optional = false -python-versions = "*" -files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, -] - -[[package]] -name = "execnet" -version = "2.0.2" -description = "execnet: rapid multi-Python deployment" -optional = false -python-versions = ">=3.7" -files = [ - {file = "execnet-2.0.2-py3-none-any.whl", hash = "sha256:88256416ae766bc9e8895c76a87928c0012183da3cc4fc18016e6f050e025f41"}, - {file = "execnet-2.0.2.tar.gz", hash = "sha256:cc59bc4423742fd71ad227122eb0dd44db51efb3dc4095b45ac9a08c770096af"}, -] - -[package.extras] -testing = ["hatch", "pre-commit", "pytest", "tox"] - -[[package]] -name = "filelock" -version = "3.13.1" -description = "A platform independent file lock." -optional = false -python-versions = ">=3.8" -files = [ - {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, - {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, -] - -[package.extras] -docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] -typing = ["typing-extensions (>=4.8)"] - -[[package]] -name = "freezegun" -version = "1.4.0" -description = "Let your Python tests travel through time" -optional = false -python-versions = ">=3.7" -files = [ - {file = "freezegun-1.4.0-py3-none-any.whl", hash = "sha256:55e0fc3c84ebf0a96a5aa23ff8b53d70246479e9a68863f1fcac5a3e52f19dd6"}, - {file = "freezegun-1.4.0.tar.gz", hash = "sha256:10939b0ba0ff5adaecf3b06a5c2f73071d9678e507c5eaedb23c761d56ac774b"}, -] - -[package.dependencies] -python-dateutil = ">=2.7" - -[[package]] -name = "frozenlist" -version = "1.4.1" -description = "A list-like structure which implements collections.abc.MutableSequence" -optional = false -python-versions = ">=3.8" -files = [ - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, - {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, - {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, - {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, - {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, - {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, - {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, - {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, - {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, - {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, - {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, - {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, - {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, - {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, - {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, - {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, - {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, - {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, - {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, - {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, - {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, - {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, - {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, - {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, - {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, - {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, - {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, - {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, -] - -[[package]] -name = "greenlet" -version = "3.0.3" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.7" -files = [ - {file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"}, - {file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"}, - {file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"}, - {file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"}, - {file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"}, - {file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"}, - {file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"}, - {file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"}, - {file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"}, - {file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"}, - {file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"}, - {file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"}, - {file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"}, - {file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"}, - {file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"}, - {file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"}, - {file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"}, - {file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"}, - {file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"}, - {file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"}, - {file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"}, - {file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"}, - {file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"}, - {file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"}, - {file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"}, - {file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"}, - {file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"}, - {file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"}, -] - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil"] - -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.7" -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - -[[package]] -name = "habluetooth" -version = "2.0.2" -description = "High availability Bluetooth" -optional = false -python-versions = ">=3.10,<3.13" -files = [ - {file = "habluetooth-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:dab2a3f2b2bf28bbe8068c100a46c7f428fa452aa9ace63903ea891bf40d3246"}, - {file = "habluetooth-2.0.2-cp310-cp310-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:3e131ee8f12c29fb32f51d50fe4ad878ae252c64b4386ef3ea0a3665605215a1"}, - {file = "habluetooth-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdabf5ad9db3c72819e3031168b5372988d4f313da195d61e0ab6c3136dbc866"}, - {file = "habluetooth-2.0.2-cp310-cp310-manylinux_2_35_x86_64.whl", hash = "sha256:8190ac37b021e7edf99e7968d0be7b617c2419d92eb3a706bfbc4bbd3c9a1774"}, - {file = "habluetooth-2.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:820b2001c266a9bc9840ab5b08a00a85bc1d3df0f2645ca7f998f8a2bfaa3f10"}, - {file = "habluetooth-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:98a9a62bb3a4ad86b3de02dcb2b31f9b0ef7f12f2b82cee3ae227f790aae4e65"}, - {file = "habluetooth-2.0.2-cp310-cp310-win32.whl", hash = "sha256:6cb2c77ad3e8c193157e4f201b825ff63c0e04b00392cdf8635bf7167c7227c7"}, - {file = "habluetooth-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a996604430c3d97901ac3331712927e7f32861df3fa505b8ee73448099b15497"}, - {file = "habluetooth-2.0.2-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:e61893522b0305e16a244dcfb2292d0bb6f6a6603923658f33a36b9ae779524a"}, - {file = "habluetooth-2.0.2-cp311-cp311-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:504ba9a8cba38d7137cb876920775a046e8880d4ab51dee593d9998f711271e0"}, - {file = "habluetooth-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698a6cc6fb0cb996ccddc0f0f302107d6dd93248b70b23f42ff54f58074389ad"}, - {file = "habluetooth-2.0.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2e13dd58ba8bbd749c0bdb3ff1746e711a1161d2e578f6cb7de096c0ad4f3abb"}, - {file = "habluetooth-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8e30f6fc90ef5bbb2504c843a4cbdce69bb1818ba4fa155fe867dd5d5d3c71ff"}, - {file = "habluetooth-2.0.2-cp311-cp311-win32.whl", hash = "sha256:0fff260d8baa800182143f489f9e2bf054d1734393e20b1e8393bcc90406ab5f"}, - {file = "habluetooth-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:ebf651ae6a6950da6d88a166458ceffc0a96d8cf3a90e50183db8a05c52f2442"}, - {file = "habluetooth-2.0.2-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:fde406bd9eac30067cd9b277629fe96ec53e446502cd56edd0ecc3b57a922bd8"}, - {file = "habluetooth-2.0.2-cp312-cp312-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:36472e7a052233eedd7364890ff2e76933579e3167fa1726bcaf978a1c4c1856"}, - {file = "habluetooth-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c91413e63bf71e958f2de29cef2596cddb29047c8b0074b6c9c283fde1e6c46b"}, - {file = "habluetooth-2.0.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:c75542a3aa397aa9045217a86ec83c22e6ae1671ed4026d0e8c00a75d7b6f96f"}, - {file = "habluetooth-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:01042de78ce05e662c2394f2616f02ae4072a64df8ae83610cde811420984b47"}, - {file = "habluetooth-2.0.2-cp312-cp312-win32.whl", hash = "sha256:dd93e549376c854ae9d2113d351f880d67e7eba8cc8a19534cbbd5aeb017f295"}, - {file = "habluetooth-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:681d718dd5f5263d7dea828f51e5963ec9db3cc9b7631ae6a102f6f7b90c251f"}, - {file = "habluetooth-2.0.2-pp310-pypy310_pp73-macosx_11_0_x86_64.whl", hash = "sha256:17a28ee3158a255192845e9067ee4979b98630e4d4d328e18bcb8cba0c3b0b5a"}, - {file = "habluetooth-2.0.2-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux_2_5_i686.manylinux1_i686.manylinux2014_i686.whl", hash = "sha256:e4d2764fce635d512d01f98e4f287442efd5b8dc759dbd346d84a710887e543d"}, - {file = "habluetooth-2.0.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d46850376833bbd36abbf49ba6199eb754d731924028a58b91d48a5cfa057508"}, - {file = "habluetooth-2.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d7f4cc53ba79b99ba8461128166d1453a851f64f4c39b78bfe8443337378b4a1"}, - {file = "habluetooth-2.0.2.tar.gz", hash = "sha256:ac9fd696dc35f4e3a9cc85d7b51c619891c4d6070b684604b74efdc02e8d4d06"}, -] - -[package.dependencies] -bleak = ">=0.21.1" -bleak-retry-connector = ">=3.3.0" -bluetooth-adapters = ">=0.16.1" -bluetooth-auto-recovery = ">=1.2.3" -bluetooth-data-tools = ">=1.16.0" - -[[package]] -name = "home-assistant-bluetooth" -version = "1.12.0" -description = "Home Assistant Bluetooth Models and Helpers" -optional = false -python-versions = ">=3.11,<3.13" -files = [ - {file = "home_assistant_bluetooth-1.12.0-py3-none-any.whl", hash = "sha256:9b3cea07e50f0521d805a3fef102a338aaeb532576f100844a155b890ac97381"}, - {file = "home_assistant_bluetooth-1.12.0.tar.gz", hash = "sha256:9cac369fea11f67c1165df527f918a8c56630cbccb3c5cbe24b72f06aeb9f59b"}, -] - -[package.dependencies] -habluetooth = ">=0.11.0" - -[[package]] -name = "homeassistant" -version = "2024.3.0b6" -description = "Open-source home automation platform running on Python 3." -optional = false -python-versions = ">=3.11.0" -files = [ - {file = "homeassistant-2024.3.0b6-py3-none-any.whl", hash = "sha256:eb61f5dfdf6dd7e66e4cc849fb77ea1a6ccaf82b6d9d9874d9aa21c00ca24683"}, - {file = "homeassistant-2024.3.0b6.tar.gz", hash = "sha256:de384a0dd94c29805411a8b4c749f883e0377364875513d04ff5b391103f2d25"}, -] - -[package.dependencies] -aiohttp = "3.9.3" -aiohttp-cors = "0.7.0" -aiohttp-fast-url-dispatcher = "0.3.0" -aiohttp-zlib-ng = "0.3.1" -astral = "2.2" -async-interrupt = "1.1.1" -atomicwrites-homeassistant = "1.4.1" -attrs = "23.2.0" -awesomeversion = "24.2.0" -bcrypt = "4.1.2" -certifi = ">=2021.5.30" -ciso8601 = "2.3.1" -cryptography = "42.0.5" -home-assistant-bluetooth = "1.12.0" -httpx = "0.27.0" -ifaddr = "0.2.0" -Jinja2 = "3.1.3" -lru-dict = "1.3.0" -orjson = "3.9.15" -packaging = ">=23.1" -pip = ">=21.3.1" -PyJWT = "2.8.0" -pyOpenSSL = "24.0.0" -python-slugify = "8.0.4" -PyYAML = "6.0.1" -requests = "2.31.0" -typing-extensions = ">=4.10.0,<5.0" -ulid-transform = "0.9.0" -urllib3 = ">=1.26.5,<2" -voluptuous = "0.13.1" -voluptuous-serialize = "2.6.0" -yarl = "1.9.4" - -[[package]] -name = "homeassistant-stubs" -version = "2024.3.0b6" -description = "PEP 484 typing stubs for Home Assistant Core" -optional = false -python-versions = ">=3.11,<3.13" -files = [ - {file = "homeassistant_stubs-2024.3.0b6-py3-none-any.whl", hash = "sha256:f02f73ed9064600cc2b81387c3206b5f4bd72b0af6df32ea9aa6cdecdabbfac3"}, - {file = "homeassistant_stubs-2024.3.0b6.tar.gz", hash = "sha256:b7f6544935f7abad16ef45eba0fc6a4304c18ef1514331f106b67ecab792e96d"}, -] - -[package.dependencies] -homeassistant = "2024.3.0b6" - -[[package]] -name = "httpcore" -version = "1.0.2" -description = "A minimal low-level HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpcore-1.0.2-py3-none-any.whl", hash = "sha256:096cc05bca73b8e459a1fc3dcf585148f63e534eae4339559c9b8a8d6399acc7"}, - {file = "httpcore-1.0.2.tar.gz", hash = "sha256:9fc092e4799b26174648e54b74ed5f683132a464e95643b226e00c2ed2fa6535"}, -] - -[package.dependencies] -certifi = "*" -h11 = ">=0.13,<0.15" - -[package.extras] -asyncio = ["anyio (>=4.0,<5.0)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] -trio = ["trio (>=0.22.0,<0.23.0)"] - -[[package]] -name = "httpx" -version = "0.27.0" -description = "The next generation HTTP client." -optional = false -python-versions = ">=3.8" -files = [ - {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, - {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, -] - -[package.dependencies] -anyio = "*" -certifi = "*" -httpcore = "==1.*" -idna = "*" -sniffio = "*" - -[package.extras] -brotli = ["brotli", "brotlicffi"] -cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] -http2 = ["h2 (>=3,<5)"] -socks = ["socksio (==1.*)"] - -[[package]] -name = "identify" -version = "2.5.33" -description = "File identification library for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"}, - {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"}, -] - -[package.extras] -license = ["ukkonen"] - -[[package]] -name = "idna" -version = "3.7" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.5" -files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, -] - -[[package]] -name = "ifaddr" -version = "0.2.0" -description = "Cross-platform network interface and IP address enumeration library" -optional = false -python-versions = "*" -files = [ - {file = "ifaddr-0.2.0-py3-none-any.whl", hash = "sha256:085e0305cfe6f16ab12d72e2024030f5d52674afad6911bb1eee207177b8a748"}, - {file = "ifaddr-0.2.0.tar.gz", hash = "sha256:cc0cbfcaabf765d44595825fb96a99bb12c79716b73b44330ea38ee2b0c4aed4"}, -] - -[[package]] -name = "iniconfig" -version = "2.0.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.7" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "jinja2" -version = "3.1.3" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -files = [ - {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, - {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "lru-dict" -version = "1.3.0" -description = "An Dict like LRU container." -optional = false -python-versions = ">=3.8" -files = [ - {file = "lru-dict-1.3.0.tar.gz", hash = "sha256:54fd1966d6bd1fcde781596cb86068214edeebff1db13a2cea11079e3fd07b6b"}, - {file = "lru_dict-1.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4073333894db9840f066226d50e6f914a2240711c87d60885d8c940b69a6673f"}, - {file = "lru_dict-1.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0ad6361e4dd63b47b2fc8eab344198f37387e1da3dcfacfee19bafac3ec9f1eb"}, - {file = "lru_dict-1.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c637ab54b8cd9802fe19b260261e38820d748adf7606e34045d3c799b6dde813"}, - {file = "lru_dict-1.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fce5f95489ca1fc158cc9fe0f4866db9cec82c2be0470926a9080570392beaf"}, - {file = "lru_dict-1.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2bf2e24cf5f19c3ff69bf639306e83dced273e6fa775b04e190d7f5cd16f794"}, - {file = "lru_dict-1.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e90059f7701bef3c4da073d6e0434a9c7dc551d5adce30e6b99ef86b186f4b4a"}, - {file = "lru_dict-1.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ecb7ae557239c64077e9b26a142eb88e63cddb104111a5122de7bebbbd00098"}, - {file = "lru_dict-1.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6af36166d22dba851e06a13e35bbf33845d3dd88872e6aebbc8e3e7db70f4682"}, - {file = "lru_dict-1.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ee38d420c77eed548df47b7d74b5169a98e71c9e975596e31ab808e76d11f09"}, - {file = "lru_dict-1.3.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0e1845024c31e6ff246c9eb5e6f6f1a8bb564c06f8a7d6d031220044c081090b"}, - {file = "lru_dict-1.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3ca5474b1649555d014be1104e5558a92497509021a5ba5ea6e9b492303eb66b"}, - {file = "lru_dict-1.3.0-cp310-cp310-win32.whl", hash = "sha256:ebb03a9bd50c2ed86d4f72a54e0aae156d35a14075485b2127c4b01a3f4a63fa"}, - {file = "lru_dict-1.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:04cda617f4e4c27009005d0a8185ef02829b14b776d2791f5c994cc9d668bc24"}, - {file = "lru_dict-1.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:20c595764695d20bdc3ab9b582e0cc99814da183544afb83783a36d6741a0dac"}, - {file = "lru_dict-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d9b30a8f50c3fa72a494eca6be5810a1b5c89e4f0fda89374f0d1c5ad8d37d51"}, - {file = "lru_dict-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9710737584650a4251b9a566cbb1a86f83437adb209c9ba43a4e756d12faf0d7"}, - {file = "lru_dict-1.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b84c321ae34f2f40aae80e18b6fa08b31c90095792ab64bb99d2e385143effaa"}, - {file = "lru_dict-1.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eed24272b4121b7c22f234daed99899817d81d671b3ed030c876ac88bc9dc890"}, - {file = "lru_dict-1.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd13af06dab7c6ee92284fd02ed9a5613a07d5c1b41948dc8886e7207f86dfd"}, - {file = "lru_dict-1.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1efc59bfba6aac33684d87b9e02813b0e2445b2f1c444dae2a0b396ad0ed60c"}, - {file = "lru_dict-1.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cfaf75ac574447afcf8ad998789071af11d2bcf6f947643231f692948839bd98"}, - {file = "lru_dict-1.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c95f8751e2abd6f778da0399c8e0239321d560dbc58cb063827123137d213242"}, - {file = "lru_dict-1.3.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:abd0c284b26b5c4ee806ca4f33ab5e16b4bf4d5ec9e093e75a6f6287acdde78e"}, - {file = "lru_dict-1.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a47740652b25900ac5ce52667b2eade28d8b5fdca0ccd3323459df710e8210a"}, - {file = "lru_dict-1.3.0-cp311-cp311-win32.whl", hash = "sha256:a690c23fc353681ed8042d9fe8f48f0fb79a57b9a45daea2f0be1eef8a1a4aa4"}, - {file = "lru_dict-1.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:efd3f4e0385d18f20f7ea6b08af2574c1bfaa5cb590102ef1bee781bdfba84bc"}, - {file = "lru_dict-1.3.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:c279068f68af3b46a5d649855e1fb87f5705fe1f744a529d82b2885c0e1fc69d"}, - {file = "lru_dict-1.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:350e2233cfee9f326a0d7a08e309372d87186565e43a691b120006285a0ac549"}, - {file = "lru_dict-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4eafb188a84483b3231259bf19030859f070321b00326dcb8e8c6cbf7db4b12f"}, - {file = "lru_dict-1.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73593791047e36b37fdc0b67b76aeed439fcea80959c7d46201240f9ec3b2563"}, - {file = "lru_dict-1.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1958cb70b9542773d6241974646e5410e41ef32e5c9e437d44040d59bd80daf2"}, - {file = "lru_dict-1.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc1cd3ed2cee78a47f11f3b70be053903bda197a873fd146e25c60c8e5a32cd6"}, - {file = "lru_dict-1.3.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82eb230d48eaebd6977a92ddaa6d788f14cf4f4bcf5bbffa4ddfd60d051aa9d4"}, - {file = "lru_dict-1.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5ad659cbc349d0c9ba8e536b5f40f96a70c360f43323c29f4257f340d891531c"}, - {file = "lru_dict-1.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ba490b8972531d153ac0d4e421f60d793d71a2f4adbe2f7740b3c55dce0a12f1"}, - {file = "lru_dict-1.3.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:c0131351b8a7226c69f1eba5814cbc9d1d8daaf0fdec1ae3f30508e3de5262d4"}, - {file = "lru_dict-1.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0e88dba16695f17f41701269fa046197a3fd7b34a8dba744c8749303ddaa18df"}, - {file = "lru_dict-1.3.0-cp312-cp312-win32.whl", hash = "sha256:6ffaf595e625b388babc8e7d79b40f26c7485f61f16efe76764e32dce9ea17fc"}, - {file = "lru_dict-1.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf9da32ef2582434842ab6ba6e67290debfae72771255a8e8ab16f3e006de0aa"}, - {file = "lru_dict-1.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c265f16c936a8ff3bb4b8a4bda0be94c15ec28b63e99fdb1439c1ffe4cd437db"}, - {file = "lru_dict-1.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:784ca9d3b0730b3ec199c0a58f66264c63dd5d438119c739c349a6a9be8e5f6e"}, - {file = "lru_dict-1.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e13b2f58f647178470adaa14603bb64cc02eeed32601772ccea30e198252883c"}, - {file = "lru_dict-1.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ffbce5c2e80f57937679553c8f27e61ec327c962bf7ea0b15f1d74277fd5363"}, - {file = "lru_dict-1.3.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7969cb034b3ccc707aff877c73c225c32d7e2a7981baa8f92f5dd4d468fe8c33"}, - {file = "lru_dict-1.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca9ab676609cce85dd65d91c275e47da676d13d77faa72de286fbea30fbaa596"}, - {file = "lru_dict-1.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f27c078b5d75989952acbf9b77e14c3dadc468a4aafe85174d548afbc5efc38b"}, - {file = "lru_dict-1.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6123aefe97762ad74215d05320a7f389f196f0594c8813534284d4eafeca1a96"}, - {file = "lru_dict-1.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:cd869cadba9a63e1e7fe2dced4a5747d735135b86016b0a63e8c9e324ab629ac"}, - {file = "lru_dict-1.3.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:40a8daddc29c7edb09dfe44292cf111f1e93a8344349778721d430d336b50505"}, - {file = "lru_dict-1.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a03170e4152836987a88dcebde61aaeb73ab7099a00bb86509d45b3fe424230"}, - {file = "lru_dict-1.3.0-cp38-cp38-win32.whl", hash = "sha256:3b4f121afe10f5a82b8e317626eb1e1c325b3f104af56c9756064cd833b1950b"}, - {file = "lru_dict-1.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:1470f5828c7410e16c24b5150eb649647986e78924816e6fb0264049dea14a2b"}, - {file = "lru_dict-1.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a3c9f746a9917e784fffcedeac4c8c47a3dbd90cbe13b69e9140182ad97ce4b7"}, - {file = "lru_dict-1.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2789296819525a1f3204072dfcf3df6db8bcf69a8fc740ffd3de43a684ea7002"}, - {file = "lru_dict-1.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:170b66d29945391460351588a7bd8210a95407ae82efe0b855e945398a1d24ea"}, - {file = "lru_dict-1.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:774ca88501a9effe8797c3db5a6685cf20978c9cb0fe836b6813cfe1ca60d8c9"}, - {file = "lru_dict-1.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:df2e119c6ae412d2fd641a55f8a1e2e51f45a3de3449c18b1b86c319ab79e0c4"}, - {file = "lru_dict-1.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28aa1ea42a7e48174bf513dc2416fea7511a547961e678dc6f5670ca987c18cb"}, - {file = "lru_dict-1.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9537e1cee6fa582cb68f2fb9ce82d51faf2ccc0a638b275d033fdcb1478eb80b"}, - {file = "lru_dict-1.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:64545fca797fe2c68c5168efb5f976c6e1459e058cab02445207a079180a3557"}, - {file = "lru_dict-1.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a193a14c66cfc0c259d05dddc5e566a4b09e8f1765e941503d065008feebea9d"}, - {file = "lru_dict-1.3.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:3cb1de0ce4137b060abaafed8474cc0ebd12cedd88aaa7f7b3ebb1ddfba86ae0"}, - {file = "lru_dict-1.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8551ccab1349d4bebedab333dfc8693c74ff728f4b565fe15a6bf7d296bd7ea9"}, - {file = "lru_dict-1.3.0-cp39-cp39-win32.whl", hash = "sha256:6cb0be5e79c3f34d69b90d8559f0221e374b974b809a22377122c4b1a610ff67"}, - {file = "lru_dict-1.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9f725f2a0bdf1c18735372d5807af4ea3b77888208590394d4660e3d07971f21"}, - {file = "lru_dict-1.3.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f8f7824db5a64581180ab9d09842e6dd9fcdc46aac9cb592a0807cd37ea55680"}, - {file = "lru_dict-1.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acd04b7e7b0c0c192d738df9c317093335e7282c64c9d1bb6b7ebb54674b4e24"}, - {file = "lru_dict-1.3.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5c20f236f27551e3f0adbf1a987673fb1e9c38d6d284502cd38f5a3845ef681"}, - {file = "lru_dict-1.3.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca3703ff03b03a1848c563bc2663d0ad813c1cd42c4d9cf75b623716d4415d9a"}, - {file = "lru_dict-1.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a9fb71ba262c6058a0017ce83d343370d0a0dbe2ae62c2eef38241ec13219330"}, - {file = "lru_dict-1.3.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f5b88a7c39e307739a3701194993455968fcffe437d1facab93546b1b8a334c1"}, - {file = "lru_dict-1.3.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2682bfca24656fb7a643621520d57b7fe684ed5fa7be008704c1235d38e16a32"}, - {file = "lru_dict-1.3.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96fc87ddf569181827458ec5ad8fa446c4690cffacda66667de780f9fcefd44d"}, - {file = "lru_dict-1.3.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcec98e2c7da7631f0811730303abc4bdfe70d013f7a11e174a2ccd5612a7c59"}, - {file = "lru_dict-1.3.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6bba2863060caeaedd8386b0c8ee9a7ce4d57a7cb80ceeddf440b4eff2d013ba"}, - {file = "lru_dict-1.3.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3c497fb60279f1e1d7dfbe150b1b069eaa43f7e172dab03f206282f4994676c5"}, - {file = "lru_dict-1.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d9509d817a47597988615c1a322580c10100acad10c98dfcf3abb41e0e5877f"}, - {file = "lru_dict-1.3.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0213ab4e3d9a8d386c18e485ad7b14b615cb6f05df6ef44fb2a0746c6ea9278b"}, - {file = "lru_dict-1.3.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b50fbd69cd3287196796ab4d50e4cc741eb5b5a01f89d8e930df08da3010c385"}, - {file = "lru_dict-1.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5247d1f011f92666010942434020ddc5a60951fefd5d12a594f0e5d9f43e3b3b"}, -] - -[package.extras] -test = ["pytest"] - -[[package]] -name = "mac-vendor-lookup" -version = "0.1.12" -description = "Find the vendor for a given MAC address" -optional = false -python-versions = "<4, >=3.5" -files = [ - {file = "mac_vendor_lookup-0.1.12-py3-none-any.whl", hash = "sha256:aeec6eac01b07e6558d889b51f475a1e1e938e09cab409a069ab6a43b13cba58"}, - {file = "mac_vendor_lookup-0.1.12.tar.gz", hash = "sha256:74e1723e177d4deb02977148d3fa04a7916f4bf93268e2afe3240529272bf80d"}, -] - -[package.dependencies] -aiofiles = "*" -aiohttp = "*" - -[package.extras] -test = ["coverage", "pytest"] - -[[package]] -name = "markupsafe" -version = "2.1.3" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.7" -files = [ - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, - {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, - {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, - {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, - {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, - {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, - {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, -] - -[[package]] -name = "mock-open" -version = "1.4.0" -description = "A better mock for file I/O" -optional = false -python-versions = "*" -files = [ - {file = "mock-open-1.4.0.tar.gz", hash = "sha256:c3ecb6b8c32a5899a4f5bf4495083b598b520c698bba00e1ce2ace6e9c239100"}, -] - -[[package]] -name = "multidict" -version = "6.0.4" -description = "multidict implementation" -optional = false -python-versions = ">=3.7" -files = [ - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, - {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, - {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, - {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, - {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, - {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, - {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, - {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, - {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, - {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, - {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, - {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, - {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, -] - -[[package]] -name = "mypy" -version = "1.8.0" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "mypy-1.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3"}, - {file = "mypy-1.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4"}, - {file = "mypy-1.8.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d"}, - {file = "mypy-1.8.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9"}, - {file = "mypy-1.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae"}, - {file = "mypy-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3"}, - {file = "mypy-1.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817"}, - {file = "mypy-1.8.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d"}, - {file = "mypy-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd"}, - {file = "mypy-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55"}, - {file = "mypy-1.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218"}, - {file = "mypy-1.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3"}, - {file = "mypy-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e"}, - {file = "mypy-1.8.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6"}, - {file = "mypy-1.8.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66"}, - {file = "mypy-1.8.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6"}, - {file = "mypy-1.8.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d"}, - {file = "mypy-1.8.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02"}, - {file = "mypy-1.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8"}, - {file = "mypy-1.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259"}, - {file = "mypy-1.8.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b"}, - {file = "mypy-1.8.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592"}, - {file = "mypy-1.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a"}, - {file = "mypy-1.8.0-py3-none-any.whl", hash = "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d"}, - {file = "mypy-1.8.0.tar.gz", hash = "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07"}, -] - -[package.dependencies] -mypy-extensions = ">=1.0.0" -typing-extensions = ">=4.1.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.5" -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "nodeenv" -version = "1.8.0" -description = "Node.js virtual environment builder" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" -files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, -] - -[package.dependencies] -setuptools = "*" - -[[package]] -name = "numpy" -version = "1.26.0" -description = "Fundamental package for array computing in Python" -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"}, -] - -[[package]] -name = "orjson" -version = "3.9.15" -description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" -optional = false -python-versions = ">=3.8" -files = [ - {file = "orjson-3.9.15-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d61f7ce4727a9fa7680cd6f3986b0e2c732639f46a5e0156e550e35258aa313a"}, - {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4feeb41882e8aa17634b589533baafdceb387e01e117b1ec65534ec724023d04"}, - {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fbbeb3c9b2edb5fd044b2a070f127a0ac456ffd079cb82746fc84af01ef021a4"}, - {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b66bcc5670e8a6b78f0313bcb74774c8291f6f8aeef10fe70e910b8040f3ab75"}, - {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2973474811db7b35c30248d1129c64fd2bdf40d57d84beed2a9a379a6f57d0ab"}, - {file = "orjson-3.9.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fe41b6f72f52d3da4db524c8653e46243c8c92df826ab5ffaece2dba9cccd58"}, - {file = "orjson-3.9.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4228aace81781cc9d05a3ec3a6d2673a1ad0d8725b4e915f1089803e9efd2b99"}, - {file = "orjson-3.9.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f7b65bfaf69493c73423ce9db66cfe9138b2f9ef62897486417a8fcb0a92bfe"}, - {file = "orjson-3.9.15-cp310-none-win32.whl", hash = "sha256:2d99e3c4c13a7b0fb3792cc04c2829c9db07838fb6973e578b85c1745e7d0ce7"}, - {file = "orjson-3.9.15-cp310-none-win_amd64.whl", hash = "sha256:b725da33e6e58e4a5d27958568484aa766e825e93aa20c26c91168be58e08cbb"}, - {file = "orjson-3.9.15-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c8e8fe01e435005d4421f183038fc70ca85d2c1e490f51fb972db92af6e047c2"}, - {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87f1097acb569dde17f246faa268759a71a2cb8c96dd392cd25c668b104cad2f"}, - {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff0f9913d82e1d1fadbd976424c316fbc4d9c525c81d047bbdd16bd27dd98cfc"}, - {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8055ec598605b0077e29652ccfe9372247474375e0e3f5775c91d9434e12d6b1"}, - {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d6768a327ea1ba44c9114dba5fdda4a214bdb70129065cd0807eb5f010bfcbb5"}, - {file = "orjson-3.9.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12365576039b1a5a47df01aadb353b68223da413e2e7f98c02403061aad34bde"}, - {file = "orjson-3.9.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:71c6b009d431b3839d7c14c3af86788b3cfac41e969e3e1c22f8a6ea13139404"}, - {file = "orjson-3.9.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e18668f1bd39e69b7fed19fa7cd1cd110a121ec25439328b5c89934e6d30d357"}, - {file = "orjson-3.9.15-cp311-none-win32.whl", hash = "sha256:62482873e0289cf7313461009bf62ac8b2e54bc6f00c6fabcde785709231a5d7"}, - {file = "orjson-3.9.15-cp311-none-win_amd64.whl", hash = "sha256:b3d336ed75d17c7b1af233a6561cf421dee41d9204aa3cfcc6c9c65cd5bb69a8"}, - {file = "orjson-3.9.15-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:82425dd5c7bd3adfe4e94c78e27e2fa02971750c2b7ffba648b0f5d5cc016a73"}, - {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c51378d4a8255b2e7c1e5cc430644f0939539deddfa77f6fac7b56a9784160a"}, - {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6ae4e06be04dc00618247c4ae3f7c3e561d5bc19ab6941427f6d3722a0875ef7"}, - {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcef128f970bb63ecf9a65f7beafd9b55e3aaf0efc271a4154050fc15cdb386e"}, - {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b72758f3ffc36ca566ba98a8e7f4f373b6c17c646ff8ad9b21ad10c29186f00d"}, - {file = "orjson-3.9.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c57bc7b946cf2efa67ac55766e41764b66d40cbd9489041e637c1304400494"}, - {file = "orjson-3.9.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:946c3a1ef25338e78107fba746f299f926db408d34553b4754e90a7de1d44068"}, - {file = "orjson-3.9.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2f256d03957075fcb5923410058982aea85455d035607486ccb847f095442bda"}, - {file = "orjson-3.9.15-cp312-none-win_amd64.whl", hash = "sha256:5bb399e1b49db120653a31463b4a7b27cf2fbfe60469546baf681d1b39f4edf2"}, - {file = "orjson-3.9.15-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b17f0f14a9c0ba55ff6279a922d1932e24b13fc218a3e968ecdbf791b3682b25"}, - {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f6cbd8e6e446fb7e4ed5bac4661a29e43f38aeecbf60c4b900b825a353276a1"}, - {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:76bc6356d07c1d9f4b782813094d0caf1703b729d876ab6a676f3aaa9a47e37c"}, - {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fdfa97090e2d6f73dced247a2f2d8004ac6449df6568f30e7fa1a045767c69a6"}, - {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7413070a3e927e4207d00bd65f42d1b780fb0d32d7b1d951f6dc6ade318e1b5a"}, - {file = "orjson-3.9.15-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cf1596680ac1f01839dba32d496136bdd5d8ffb858c280fa82bbfeb173bdd40"}, - {file = "orjson-3.9.15-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:809d653c155e2cc4fd39ad69c08fdff7f4016c355ae4b88905219d3579e31eb7"}, - {file = "orjson-3.9.15-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:920fa5a0c5175ab14b9c78f6f820b75804fb4984423ee4c4f1e6d748f8b22bc1"}, - {file = "orjson-3.9.15-cp38-none-win32.whl", hash = "sha256:2b5c0f532905e60cf22a511120e3719b85d9c25d0e1c2a8abb20c4dede3b05a5"}, - {file = "orjson-3.9.15-cp38-none-win_amd64.whl", hash = "sha256:67384f588f7f8daf040114337d34a5188346e3fae6c38b6a19a2fe8c663a2f9b"}, - {file = "orjson-3.9.15-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6fc2fe4647927070df3d93f561d7e588a38865ea0040027662e3e541d592811e"}, - {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34cbcd216e7af5270f2ffa63a963346845eb71e174ea530867b7443892d77180"}, - {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f541587f5c558abd93cb0de491ce99a9ef8d1ae29dd6ab4dbb5a13281ae04cbd"}, - {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92255879280ef9c3c0bcb327c5a1b8ed694c290d61a6a532458264f887f052cb"}, - {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:05a1f57fb601c426635fcae9ddbe90dfc1ed42245eb4c75e4960440cac667262"}, - {file = "orjson-3.9.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ede0bde16cc6e9b96633df1631fbcd66491d1063667f260a4f2386a098393790"}, - {file = "orjson-3.9.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e88b97ef13910e5f87bcbc4dd7979a7de9ba8702b54d3204ac587e83639c0c2b"}, - {file = "orjson-3.9.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:57d5d8cf9c27f7ef6bc56a5925c7fbc76b61288ab674eb352c26ac780caa5b10"}, - {file = "orjson-3.9.15-cp39-none-win32.whl", hash = "sha256:001f4eb0ecd8e9ebd295722d0cbedf0748680fb9998d3993abaed2f40587257a"}, - {file = "orjson-3.9.15-cp39-none-win_amd64.whl", hash = "sha256:ea0b183a5fe6b2b45f3b854b0d19c4e932d6f5934ae1f723b07cf9560edd4ec7"}, - {file = "orjson-3.9.15.tar.gz", hash = "sha256:95cae920959d772f30ab36d3b25f83bb0f3be671e986c72ce22f8fa700dae061"}, -] - -[[package]] -name = "packaging" -version = "23.2" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.7" -files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, -] - -[[package]] -name = "paho-mqtt" -version = "1.6.1" -description = "MQTT version 5.0/3.1.1 client class" -optional = false -python-versions = "*" -files = [ - {file = "paho-mqtt-1.6.1.tar.gz", hash = "sha256:2a8291c81623aec00372b5a85558a372c747cbca8e9934dfe218638b8eefc26f"}, -] - -[package.extras] -proxy = ["PySocks"] - -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - -[[package]] -name = "pip" -version = "23.3.2" -description = "The PyPA recommended tool for installing Python packages." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pip-23.3.2-py3-none-any.whl", hash = "sha256:5052d7889c1f9d05224cd41741acb7c5d6fa735ab34e339624a614eaaa7e7d76"}, - {file = "pip-23.3.2.tar.gz", hash = "sha256:7fd9972f96db22c8077a1ee2691b172c8089b17a5652a44494a9ecb0d78f9149"}, -] - -[[package]] -name = "pipdeptree" -version = "2.15.1" -description = "Command line utility to show dependency tree of packages." -optional = false -python-versions = ">=3.8" -files = [ - {file = "pipdeptree-2.15.1-py3-none-any.whl", hash = "sha256:0abe6b5b9f86a79ea023a633d7526de14b64b848bbdf980c610d08d679c8329d"}, - {file = "pipdeptree-2.15.1.tar.gz", hash = "sha256:e4ae44fc9d0c747125316981804d7786e9d97bf59c128249b8ea272a7cbdecc4"}, -] - -[package.extras] -graphviz = ["graphviz (>=0.20.1)"] -test = ["covdefaults (>=2.3)", "diff-cover (>=8.0.1)", "pip (>=23.3.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "virtualenv (>=20.25,<21)"] - -[[package]] -name = "platformdirs" -version = "4.1.0" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -optional = false -python-versions = ">=3.8" -files = [ - {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, - {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, -] - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] - -[[package]] -name = "pluggy" -version = "1.3.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "pre-commit" -version = "3.6.0" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -optional = false -python-versions = ">=3.9" -files = [ - {file = "pre_commit-3.6.0-py2.py3-none-any.whl", hash = "sha256:c255039ef399049a5544b6ce13d135caba8f2c28c3b4033277a788f434308376"}, - {file = "pre_commit-3.6.0.tar.gz", hash = "sha256:d30bad9abf165f7785c15a21a1f46da7d0677cb00ee7ff4c579fd38922efe15d"}, -] - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" - -[[package]] -name = "pycparser" -version = "2.21" -description = "C parser in Python" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, -] - -[[package]] -name = "pydantic" -version = "1.10.12" -description = "Data validation and settings management using python type hints" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pydantic-1.10.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a1fcb59f2f355ec350073af41d927bf83a63b50e640f4dbaa01053a28b7a7718"}, - {file = "pydantic-1.10.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b7ccf02d7eb340b216ec33e53a3a629856afe1c6e0ef91d84a4e6f2fb2ca70fe"}, - {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fb2aa3ab3728d950bcc885a2e9eff6c8fc40bc0b7bb434e555c215491bcf48b"}, - {file = "pydantic-1.10.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:771735dc43cf8383959dc9b90aa281f0b6092321ca98677c5fb6125a6f56d58d"}, - {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ca48477862372ac3770969b9d75f1bf66131d386dba79506c46d75e6b48c1e09"}, - {file = "pydantic-1.10.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a5e7add47a5b5a40c49b3036d464e3c7802f8ae0d1e66035ea16aa5b7a3923ed"}, - {file = "pydantic-1.10.12-cp310-cp310-win_amd64.whl", hash = "sha256:e4129b528c6baa99a429f97ce733fff478ec955513630e61b49804b6cf9b224a"}, - {file = "pydantic-1.10.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0d191db0f92dfcb1dec210ca244fdae5cbe918c6050b342d619c09d31eea0cc"}, - {file = "pydantic-1.10.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:795e34e6cc065f8f498c89b894a3c6da294a936ee71e644e4bd44de048af1405"}, - {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69328e15cfda2c392da4e713443c7dbffa1505bc9d566e71e55abe14c97ddc62"}, - {file = "pydantic-1.10.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2031de0967c279df0d8a1c72b4ffc411ecd06bac607a212892757db7462fc494"}, - {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ba5b2e6fe6ca2b7e013398bc7d7b170e21cce322d266ffcd57cca313e54fb246"}, - {file = "pydantic-1.10.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2a7bac939fa326db1ab741c9d7f44c565a1d1e80908b3797f7f81a4f86bc8d33"}, - {file = "pydantic-1.10.12-cp311-cp311-win_amd64.whl", hash = "sha256:87afda5539d5140cb8ba9e8b8c8865cb5b1463924d38490d73d3ccfd80896b3f"}, - {file = "pydantic-1.10.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:549a8e3d81df0a85226963611950b12d2d334f214436a19537b2efed61b7639a"}, - {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598da88dfa127b666852bef6d0d796573a8cf5009ffd62104094a4fe39599565"}, - {file = "pydantic-1.10.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba5c4a8552bff16c61882db58544116d021d0b31ee7c66958d14cf386a5b5350"}, - {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c79e6a11a07da7374f46970410b41d5e266f7f38f6a17a9c4823db80dadf4303"}, - {file = "pydantic-1.10.12-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab26038b8375581dc832a63c948f261ae0aa21f1d34c1293469f135fa92972a5"}, - {file = "pydantic-1.10.12-cp37-cp37m-win_amd64.whl", hash = "sha256:e0a16d274b588767602b7646fa05af2782576a6cf1022f4ba74cbb4db66f6ca8"}, - {file = "pydantic-1.10.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6a9dfa722316f4acf4460afdf5d41d5246a80e249c7ff475c43a3a1e9d75cf62"}, - {file = "pydantic-1.10.12-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a73f489aebd0c2121ed974054cb2759af8a9f747de120acd2c3394cf84176ccb"}, - {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bcb8cbfccfcf02acb8f1a261143fab622831d9c0989707e0e659f77a18e0"}, - {file = "pydantic-1.10.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcfb5296d7877af406ba1547dfde9943b1256d8928732267e2653c26938cd9c"}, - {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2f9a6fab5f82ada41d56b0602606a5506aab165ca54e52bc4545028382ef1c5d"}, - {file = "pydantic-1.10.12-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dea7adcc33d5d105896401a1f37d56b47d443a2b2605ff8a969a0ed5543f7e33"}, - {file = "pydantic-1.10.12-cp38-cp38-win_amd64.whl", hash = "sha256:1eb2085c13bce1612da8537b2d90f549c8cbb05c67e8f22854e201bde5d98a47"}, - {file = "pydantic-1.10.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef6c96b2baa2100ec91a4b428f80d8f28a3c9e53568219b6c298c1125572ebc6"}, - {file = "pydantic-1.10.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c076be61cd0177a8433c0adcb03475baf4ee91edf5a4e550161ad57fc90f523"}, - {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d5a58feb9a39f481eda4d5ca220aa8b9d4f21a41274760b9bc66bfd72595b86"}, - {file = "pydantic-1.10.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5f805d2d5d0a41633651a73fa4ecdd0b3d7a49de4ec3fadf062fe16501ddbf1"}, - {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1289c180abd4bd4555bb927c42ee42abc3aee02b0fb2d1223fb7c6e5bef87dbe"}, - {file = "pydantic-1.10.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5d1197e462e0364906cbc19681605cb7c036f2475c899b6f296104ad42b9f5fb"}, - {file = "pydantic-1.10.12-cp39-cp39-win_amd64.whl", hash = "sha256:fdbdd1d630195689f325c9ef1a12900524dceb503b00a987663ff4f58669b93d"}, - {file = "pydantic-1.10.12-py3-none-any.whl", hash = "sha256:b749a43aa51e32839c9d71dc67eb1e4221bb04af1033a32e3923d46f9effa942"}, - {file = "pydantic-1.10.12.tar.gz", hash = "sha256:0fe8a415cea8f340e7a9af9c54fc71a649b43e8ca3cc732986116b3cb135d303"}, -] - -[package.dependencies] -typing-extensions = ">=4.2.0" - -[package.extras] -dotenv = ["python-dotenv (>=0.10.4)"] -email = ["email-validator (>=1.0.3)"] - -[[package]] -name = "pyjwt" -version = "2.8.0" -description = "JSON Web Token implementation in Python" -optional = false -python-versions = ">=3.7" -files = [ - {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, - {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, -] - -[package.extras] -crypto = ["cryptography (>=3.4.0)"] -dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] -docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] -tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] - -[[package]] -name = "pylint-per-file-ignores" -version = "1.3.2" -description = "A pylint plugin to ignore error codes per file." -optional = false -python-versions = ">=3.8.1,<4.0.0" -files = [ - {file = "pylint_per_file_ignores-1.3.2-py3-none-any.whl", hash = "sha256:4a2a2d7b88484ef1d1b1170029e542954f70efbab13ac3b977606ea5617d04c1"}, - {file = "pylint_per_file_ignores-1.3.2.tar.gz", hash = "sha256:3c641f69c316770749a8a353556504dae7469541cdaef38e195fe2228841451e"}, -] - -[[package]] -name = "pyobjc-core" -version = "9.2" -description = "Python<->ObjC Interoperability Module" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pyobjc-core-9.2.tar.gz", hash = "sha256:d734b9291fec91ff4e3ae38b9c6839debf02b79c07314476e87da8e90b2c68c3"}, - {file = "pyobjc_core-9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fa674a39949f5cde8e5c7bbcd24496446bfc67592b028aedbec7f81dc5fc4daa"}, - {file = "pyobjc_core-9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bbc8de304ee322a1ee530b4d2daca135a49b4a49aa3cedc6b2c26c43885f4842"}, - {file = "pyobjc_core-9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0fa950f092673883b8bd28bc18397415cabb457bf410920762109b411789ade9"}, - {file = "pyobjc_core-9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:586e4cae966282eaa61b21cae66ccdcee9d69c036979def26eebdc08ddebe20f"}, - {file = "pyobjc_core-9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:41189c2c680931c0395a55691763c481fc681f454f21bb4f1644f98c24a45954"}, - {file = "pyobjc_core-9.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:2d23ee539f2ba5e9f5653d75a13f575c7e36586fc0086792739e69e4c2617eda"}, - {file = "pyobjc_core-9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b9809cf96678797acb72a758f34932fe8e2602d5ab7abec15c5ac68ddb481720"}, -] - -[[package]] -name = "pyobjc-framework-cocoa" -version = "9.2" -description = "Wrappers for the Cocoa frameworks on macOS" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pyobjc-framework-Cocoa-9.2.tar.gz", hash = "sha256:efd78080872d8c8de6c2b97e0e4eac99d6203a5d1637aa135d071d464eb2db53"}, - {file = "pyobjc_framework_Cocoa-9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9e02d8a7cc4eb7685377c50ba4f17345701acf4c05b1e7480d421bff9e2f62a4"}, - {file = "pyobjc_framework_Cocoa-9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3b1e6287b3149e4c6679cdbccd8e9ef6557a4e492a892e80a77df143f40026d2"}, - {file = "pyobjc_framework_Cocoa-9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:312977ce2e3989073c6b324c69ba24283de206fe7acd6dbbbaf3e29238a22537"}, - {file = "pyobjc_framework_Cocoa-9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:aae7841cf40c26dd915f4dd828f91c6616e6b7998630b72e704750c09e00f334"}, - {file = "pyobjc_framework_Cocoa-9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:739a421e14382a46cbeb9a883f192dceff368ad28ec34d895c48c0ad34cf2c1d"}, - {file = "pyobjc_framework_Cocoa-9.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:32d9ac1033fac1b821ddee8c68f972a7074ad8c50bec0bea9a719034c1c2fb94"}, - {file = "pyobjc_framework_Cocoa-9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b236bb965e41aeb2e215d4e98a5a230d4b63252c6d26e00924ea2e69540a59d6"}, -] - -[package.dependencies] -pyobjc-core = ">=9.2" - -[[package]] -name = "pyobjc-framework-corebluetooth" -version = "9.2" -description = "Wrappers for the framework CoreBluetooth on macOS" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pyobjc-framework-CoreBluetooth-9.2.tar.gz", hash = "sha256:cb2481b1dfe211ae9ce55f36537dc8155dbf0dc8ff26e0bc2e13f7afb0a291d1"}, - {file = "pyobjc_framework_CoreBluetooth-9.2-cp36-abi3-macosx_10_9_universal2.whl", hash = "sha256:53d888742119d0f0c725d0b0c2389f68e8f21f0cba6d6aec288c53260a0196b6"}, - {file = "pyobjc_framework_CoreBluetooth-9.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:179532882126526e38fe716a50fb0ee8f440e0b838d290252c515e622b5d0e49"}, - {file = "pyobjc_framework_CoreBluetooth-9.2-cp36-abi3-macosx_11_0_universal2.whl", hash = "sha256:256a5031ea9d8a7406541fa1b0dfac549b1de93deae8284605f9355b13fb58be"}, -] - -[package.dependencies] -pyobjc-core = ">=9.2" -pyobjc-framework-Cocoa = ">=9.2" - -[[package]] -name = "pyobjc-framework-libdispatch" -version = "9.2" -description = "Wrappers for libdispatch on macOS" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pyobjc-framework-libdispatch-9.2.tar.gz", hash = "sha256:542e7f7c2b041939db5ed6f3119c1d67d73ec14a996278b92485f8513039c168"}, - {file = "pyobjc_framework_libdispatch-9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88d4091d4bcb5702783d6e86b4107db973425a17d1de491543f56bd348909b60"}, - {file = "pyobjc_framework_libdispatch-9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1a67b007113328538b57893cc7829a722270764cdbeae6d5e1460a1d911314df"}, - {file = "pyobjc_framework_libdispatch-9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6fccea1a57436cf1ac50d9ebc6e3e725bcf77f829ba6b118e62e6ed7866d359d"}, - {file = "pyobjc_framework_libdispatch-9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6eba747b7ad91b0463265a7aee59235bb051fb97687f35ca2233690369b5e4e4"}, - {file = "pyobjc_framework_libdispatch-9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2e835495860d04f63c2d2f73ae3dd79da4222864c107096dc0f99e8382700026"}, - {file = "pyobjc_framework_libdispatch-9.2-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1b107e5c3580b09553030961ea6b17abad4a5132101eab1af3ad2cb36d0f08bb"}, - {file = "pyobjc_framework_libdispatch-9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:83cdb672acf722717b5ecf004768f215f02ac02d7f7f2a9703da6e921ab02222"}, -] - -[package.dependencies] -pyobjc-core = ">=9.2" - -[[package]] -name = "pyopenssl" -version = "24.0.0" -description = "Python wrapper module around the OpenSSL library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pyOpenSSL-24.0.0-py3-none-any.whl", hash = "sha256:ba07553fb6fd6a7a2259adb9b84e12302a9a8a75c44046e8bb5d3e5ee887e3c3"}, - {file = "pyOpenSSL-24.0.0.tar.gz", hash = "sha256:6aa33039a93fffa4563e655b61d11364d01264be8ccb49906101e02a334530bf"}, -] - -[package.dependencies] -cryptography = ">=41.0.5,<43" - -[package.extras] -docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx-rtd-theme"] -test = ["flaky", "pretend", "pytest (>=3.0.1)"] - -[[package]] -name = "pyric" -version = "0.1.6.3" -description = "Python Wireless Library" -optional = false -python-versions = "*" -files = [ - {file = "PyRIC-0.1.6.3.tar.gz", hash = "sha256:b539b01cafebd2406c00097f94525ea0f8ecd1dd92f7731f43eac0ef16c2ccc9"}, -] - -[[package]] -name = "pytest" -version = "8.0.2" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-8.0.2-py3-none-any.whl", hash = "sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096"}, - {file = "pytest-8.0.2.tar.gz", hash = "sha256:d4051d623a2e0b7e51960ba963193b09ce6daeb9759a451844a21e4ddedfc1bd"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=1.3.0,<2.0" - -[package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] - -[[package]] -name = "pytest-aiohttp" -version = "1.0.5" -description = "Pytest plugin for aiohttp support" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-aiohttp-1.0.5.tar.gz", hash = "sha256:880262bc5951e934463b15e3af8bb298f11f7d4d3ebac970aab425aff10a780a"}, - {file = "pytest_aiohttp-1.0.5-py3-none-any.whl", hash = "sha256:63a5360fd2f34dda4ab8e6baee4c5f5be4cd186a403cabd498fced82ac9c561e"}, -] - -[package.dependencies] -aiohttp = ">=3.8.1" -pytest = ">=6.1.0" -pytest-asyncio = ">=0.17.2" - -[package.extras] -testing = ["coverage (==6.2)", "mypy (==0.931)"] - -[[package]] -name = "pytest-asyncio" -version = "0.23.5" -description = "Pytest support for asyncio" -optional = false -python-versions = ">=3.8" -files = [ - {file = "pytest-asyncio-0.23.5.tar.gz", hash = "sha256:3a048872a9c4ba14c3e90cc1aa20cbc2def7d01c7c8db3777ec281ba9c057675"}, - {file = "pytest_asyncio-0.23.5-py3-none-any.whl", hash = "sha256:4e7093259ba018d58ede7d5315131d21923a60f8a6e9ee266ce1589685c89eac"}, -] - -[package.dependencies] -pytest = ">=7.0.0,<9" - -[package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] -testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] - -[[package]] -name = "pytest-cov" -version = "4.1.0" -description = "Pytest plugin for measuring coverage." -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, - {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, -] - -[package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] - -[[package]] -name = "pytest-freezer" -version = "0.4.8" -description = "Pytest plugin providing a fixture interface for spulec/freezegun" -optional = false -python-versions = ">= 3.6" -files = [ - {file = "pytest_freezer-0.4.8-py3-none-any.whl", hash = "sha256:644ce7ddb8ba52b92a1df0a80a699bad2b93514c55cf92e9f2517b68ebe74814"}, - {file = "pytest_freezer-0.4.8.tar.gz", hash = "sha256:8ee2f724b3ff3540523fa355958a22e6f4c1c819928b78a7a183ae4248ce6ee6"}, -] - -[package.dependencies] -freezegun = ">=1.0" -pytest = ">=3.6" - -[[package]] -name = "pytest-homeassistant-custom-component" -version = "0.13.106" -description = "Experimental package to automatically extract test plugins for Home Assistant custom components" -optional = false -python-versions = ">=3.11" -files = [ - {file = "pytest-homeassistant-custom-component-0.13.106.tar.gz", hash = "sha256:7d06bb1412c42e4dd5fd18be0d07410cb8d5bc1edbcd5b95ecfd5842ac891160"}, - {file = "pytest_homeassistant_custom_component-0.13.106-py3-none-any.whl", hash = "sha256:4fd31ba32a59a38f0461e80a348968307a3c60f756681863ebe791ea11e23816"}, -] - -[package.dependencies] -coverage = "7.4.3" -freezegun = "1.4.0" -homeassistant = "2024.3.0b6" -mock-open = "1.4.0" -numpy = "1.26.0" -paho-mqtt = "1.6.1" -pipdeptree = "2.15.1" -pydantic = "1.10.12" -pylint-per-file-ignores = "1.3.2" -pytest = "8.0.2" -pytest-aiohttp = "1.0.5" -pytest-asyncio = "0.23.5" -pytest-cov = "4.1.0" -pytest-freezer = "0.4.8" -pytest-picked = "0.5.0" -pytest-socket = "0.7.0" -pytest-sugar = "1.0.0" -pytest-test-groups = "1.0.3" -pytest-timeout = "2.2.0" -pytest-unordered = "0.5.2" -pytest-xdist = "3.3.1" -requests-mock = "1.11.0" -respx = "0.20.2" -sqlalchemy = "2.0.27" -syrupy = "4.6.1" -tqdm = "4.66.2" - -[[package]] -name = "pytest-picked" -version = "0.5.0" -description = "Run the tests related to the changed files" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-picked-0.5.0.tar.gz", hash = "sha256:b39cd43b1f5e6efd2fc896f318e23c2c77effde8dd6efa58653a2940d8a384d9"}, - {file = "pytest_picked-0.5.0-py3-none-any.whl", hash = "sha256:6d22771a857a2cd8691fc0802f3e1371fe4063fa1ecbd216d9584bbe089fcfd3"}, -] - -[package.dependencies] -pytest = ">=3.7.0" - -[[package]] -name = "pytest-socket" -version = "0.7.0" -description = "Pytest Plugin to disable socket calls during tests" -optional = false -python-versions = ">=3.8,<4.0" -files = [ - {file = "pytest_socket-0.7.0-py3-none-any.whl", hash = "sha256:7e0f4642177d55d317bbd58fc68c6bd9048d6eadb2d46a89307fa9221336ce45"}, - {file = "pytest_socket-0.7.0.tar.gz", hash = "sha256:71ab048cbbcb085c15a4423b73b619a8b35d6a307f46f78ea46be51b1b7e11b3"}, -] - -[package.dependencies] -pytest = ">=6.2.5" - -[[package]] -name = "pytest-sugar" -version = "1.0.0" -description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)." -optional = false -python-versions = "*" -files = [ - {file = "pytest-sugar-1.0.0.tar.gz", hash = "sha256:6422e83258f5b0c04ce7c632176c7732cab5fdb909cb39cca5c9139f81276c0a"}, - {file = "pytest_sugar-1.0.0-py3-none-any.whl", hash = "sha256:70ebcd8fc5795dc457ff8b69d266a4e2e8a74ae0c3edc749381c64b5246c8dfd"}, -] - -[package.dependencies] -packaging = ">=21.3" -pytest = ">=6.2.0" -termcolor = ">=2.1.0" - -[package.extras] -dev = ["black", "flake8", "pre-commit"] - -[[package]] -name = "pytest-test-groups" -version = "1.0.3" -description = "A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups." -optional = false -python-versions = "*" -files = [ - {file = "pytest-test-groups-1.0.3.tar.gz", hash = "sha256:a93ee8ae8605ad290965508d13efc975de64f80429465837af5f3dd5bc93fd96"}, -] - -[package.dependencies] -pytest = ">=2.5" - -[[package]] -name = "pytest-timeout" -version = "2.2.0" -description = "pytest plugin to abort hanging tests" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-timeout-2.2.0.tar.gz", hash = "sha256:3b0b95dabf3cb50bac9ef5ca912fa0cfc286526af17afc806824df20c2f72c90"}, - {file = "pytest_timeout-2.2.0-py3-none-any.whl", hash = "sha256:bde531e096466f49398a59f2dde76fa78429a09a12411466f88a07213e220de2"}, -] - -[package.dependencies] -pytest = ">=5.0.0" - -[[package]] -name = "pytest-unordered" -version = "0.5.2" -description = "Test equality of unordered collections in pytest" -optional = false -python-versions = "*" -files = [ - {file = "pytest-unordered-0.5.2.tar.gz", hash = "sha256:8187e6d68a7d54e5447e88c229cbeafa38205e55baf7da7ae57cc965c1ecdbb3"}, - {file = "pytest_unordered-0.5.2-py3-none-any.whl", hash = "sha256:b01bb0e8ba80db6dd8c840fe24ad1804c8672919303dc9302688221390a7dc29"}, -] - -[package.dependencies] -pytest = ">=6.0.0" - -[[package]] -name = "pytest-xdist" -version = "3.3.1" -description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" -optional = false -python-versions = ">=3.7" -files = [ - {file = "pytest-xdist-3.3.1.tar.gz", hash = "sha256:d5ee0520eb1b7bcca50a60a518ab7a7707992812c578198f8b44fdfac78e8c93"}, - {file = "pytest_xdist-3.3.1-py3-none-any.whl", hash = "sha256:ff9daa7793569e6a68544850fd3927cd257cc03a7ef76c95e86915355e82b5f2"}, -] - -[package.dependencies] -execnet = ">=1.1" -pytest = ">=6.2.0" - -[package.extras] -psutil = ["psutil (>=3.0)"] -setproctitle = ["setproctitle"] -testing = ["filelock"] - -[[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] - -[package.dependencies] -six = ">=1.5" - -[[package]] -name = "python-slugify" -version = "8.0.4" -description = "A Python slugify application that also handles Unicode" -optional = false -python-versions = ">=3.7" -files = [ - {file = "python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856"}, - {file = "python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8"}, -] - -[package.dependencies] -text-unidecode = ">=1.3" - -[package.extras] -unidecode = ["Unidecode (>=1.1.1)"] - -[[package]] -name = "pytz" -version = "2023.3.post1" -description = "World timezone definitions, modern and historical" -optional = false -python-versions = "*" -files = [ - {file = "pytz-2023.3.post1-py2.py3-none-any.whl", hash = "sha256:ce42d816b81b68506614c11e8937d3aa9e41007ceb50bfdcb0749b921bf646c7"}, - {file = "pytz-2023.3.post1.tar.gz", hash = "sha256:7b4fddbeb94a1eba4b557da24f19fdf9db575192544270a9101d8509f9f43d7b"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.1" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {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"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {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_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {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"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {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"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {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"}, -] - -[[package]] -name = "requests" -version = "2.31.0" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.7" -files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "requests-mock" -version = "1.11.0" -description = "Mock out responses from the requests package" -optional = false -python-versions = "*" -files = [ - {file = "requests-mock-1.11.0.tar.gz", hash = "sha256:ef10b572b489a5f28e09b708697208c4a3b2b89ef80a9f01584340ea357ec3c4"}, - {file = "requests_mock-1.11.0-py2.py3-none-any.whl", hash = "sha256:f7fae383f228633f6bececebdab236c478ace2284d6292c6e7e2867b9ab74d15"}, -] - -[package.dependencies] -requests = ">=2.3,<3" -six = "*" - -[package.extras] -fixture = ["fixtures"] -test = ["fixtures", "mock", "purl", "pytest", "requests-futures", "sphinx", "testtools"] - -[[package]] -name = "respx" -version = "0.20.2" -description = "A utility for mocking out the Python HTTPX and HTTP Core libraries." -optional = false -python-versions = ">=3.7" -files = [ - {file = "respx-0.20.2-py2.py3-none-any.whl", hash = "sha256:ab8e1cf6da28a5b2dd883ea617f8130f77f676736e6e9e4a25817ad116a172c9"}, - {file = "respx-0.20.2.tar.gz", hash = "sha256:07cf4108b1c88b82010f67d3c831dae33a375c7b436e54d87737c7f9f99be643"}, -] - -[package.dependencies] -httpx = ">=0.21.0" - -[[package]] -name = "ruff" -version = "0.0.292" -description = "An extremely fast Python linter, written in Rust." -optional = false -python-versions = ">=3.7" -files = [ - {file = "ruff-0.0.292-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:02f29db018c9d474270c704e6c6b13b18ed0ecac82761e4fcf0faa3728430c96"}, - {file = "ruff-0.0.292-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:69654e564342f507edfa09ee6897883ca76e331d4bbc3676d8a8403838e9fade"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c3c91859a9b845c33778f11902e7b26440d64b9d5110edd4e4fa1726c41e0a4"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4476f1243af2d8c29da5f235c13dca52177117935e1f9393f9d90f9833f69e4"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be8eb50eaf8648070b8e58ece8e69c9322d34afe367eec4210fdee9a555e4ca7"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9889bac18a0c07018aac75ef6c1e6511d8411724d67cb879103b01758e110a81"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6bdfabd4334684a4418b99b3118793f2c13bb67bf1540a769d7816410402a205"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7c77c53bfcd75dbcd4d1f42d6cabf2485d2e1ee0678da850f08e1ab13081a8"}, - {file = "ruff-0.0.292-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e087b24d0d849c5c81516ec740bf4fd48bf363cfb104545464e0fca749b6af9"}, - {file = "ruff-0.0.292-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f160b5ec26be32362d0774964e218f3fcf0a7da299f7e220ef45ae9e3e67101a"}, - {file = "ruff-0.0.292-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ac153eee6dd4444501c4bb92bff866491d4bfb01ce26dd2fff7ca472c8df9ad0"}, - {file = "ruff-0.0.292-py3-none-musllinux_1_2_i686.whl", hash = "sha256:87616771e72820800b8faea82edd858324b29bb99a920d6aa3d3949dd3f88fb0"}, - {file = "ruff-0.0.292-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b76deb3bdbea2ef97db286cf953488745dd6424c122d275f05836c53f62d4016"}, - {file = "ruff-0.0.292-py3-none-win32.whl", hash = "sha256:e854b05408f7a8033a027e4b1c7f9889563dd2aca545d13d06711e5c39c3d003"}, - {file = "ruff-0.0.292-py3-none-win_amd64.whl", hash = "sha256:f27282bedfd04d4c3492e5c3398360c9d86a295be00eccc63914438b4ac8a83c"}, - {file = "ruff-0.0.292-py3-none-win_arm64.whl", hash = "sha256:7f67a69c8f12fbc8daf6ae6d36705037bde315abf8b82b6e1f4c9e74eb750f68"}, - {file = "ruff-0.0.292.tar.gz", hash = "sha256:1093449e37dd1e9b813798f6ad70932b57cf614e5c2b5c51005bf67d55db33ac"}, -] - -[[package]] -name = "setuptools" -version = "69.0.3" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, - {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, -] - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" -files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] - -[[package]] -name = "sniffio" -version = "1.3.0" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, -] - -[[package]] -name = "sqlalchemy" -version = "2.0.27" -description = "Database Abstraction Library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "SQLAlchemy-2.0.27-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d04e579e911562f1055d26dab1868d3e0bb905db3bccf664ee8ad109f035618a"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fa67d821c1fd268a5a87922ef4940442513b4e6c377553506b9db3b83beebbd8"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c7a596d0be71b7baa037f4ac10d5e057d276f65a9a611c46970f012752ebf2d"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:954d9735ee9c3fa74874c830d089a815b7b48df6f6b6e357a74130e478dbd951"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5cd20f58c29bbf2680039ff9f569fa6d21453fbd2fa84dbdb4092f006424c2e6"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:03f448ffb731b48323bda68bcc93152f751436ad6037f18a42b7e16af9e91c07"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-win32.whl", hash = "sha256:d997c5938a08b5e172c30583ba6b8aad657ed9901fc24caf3a7152eeccb2f1b4"}, - {file = "SQLAlchemy-2.0.27-cp310-cp310-win_amd64.whl", hash = "sha256:eb15ef40b833f5b2f19eeae65d65e191f039e71790dd565c2af2a3783f72262f"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c5bad7c60a392850d2f0fee8f355953abaec878c483dd7c3836e0089f046bf6"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3012ab65ea42de1be81fff5fb28d6db893ef978950afc8130ba707179b4284a"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dbcd77c4d94b23e0753c5ed8deba8c69f331d4fd83f68bfc9db58bc8983f49cd"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d177b7e82f6dd5e1aebd24d9c3297c70ce09cd1d5d37b43e53f39514379c029c"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:680b9a36029b30cf063698755d277885d4a0eab70a2c7c6e71aab601323cba45"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1306102f6d9e625cebaca3d4c9c8f10588735ef877f0360b5cdb4fdfd3fd7131"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-win32.whl", hash = "sha256:5b78aa9f4f68212248aaf8943d84c0ff0f74efc65a661c2fc68b82d498311fd5"}, - {file = "SQLAlchemy-2.0.27-cp311-cp311-win_amd64.whl", hash = "sha256:15e19a84b84528f52a68143439d0c7a3a69befcd4f50b8ef9b7b69d2628ae7c4"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:0de1263aac858f288a80b2071990f02082c51d88335a1db0d589237a3435fe71"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce850db091bf7d2a1f2fdb615220b968aeff3849007b1204bf6e3e50a57b3d32"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8dfc936870507da96aebb43e664ae3a71a7b96278382bcfe84d277b88e379b18"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4fbe6a766301f2e8a4519f4500fe74ef0a8509a59e07a4085458f26228cd7cc"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4535c49d961fe9a77392e3a630a626af5baa967172d42732b7a43496c8b28876"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0fb3bffc0ced37e5aa4ac2416f56d6d858f46d4da70c09bb731a246e70bff4d5"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-win32.whl", hash = "sha256:7f470327d06400a0aa7926b375b8e8c3c31d335e0884f509fe272b3c700a7254"}, - {file = "SQLAlchemy-2.0.27-cp312-cp312-win_amd64.whl", hash = "sha256:f9374e270e2553653d710ece397df67db9d19c60d2647bcd35bfc616f1622dcd"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e97cf143d74a7a5a0f143aa34039b4fecf11343eed66538610debc438685db4a"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7b5a3e2120982b8b6bd1d5d99e3025339f7fb8b8267551c679afb39e9c7c7f1"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e36aa62b765cf9f43a003233a8c2d7ffdeb55bc62eaa0a0380475b228663a38f"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5ada0438f5b74c3952d916c199367c29ee4d6858edff18eab783b3978d0db16d"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b1d9d1bfd96eef3c3faedb73f486c89e44e64e40e5bfec304ee163de01cf996f"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-win32.whl", hash = "sha256:ca891af9f3289d24a490a5fde664ea04fe2f4984cd97e26de7442a4251bd4b7c"}, - {file = "SQLAlchemy-2.0.27-cp37-cp37m-win_amd64.whl", hash = "sha256:fd8aafda7cdff03b905d4426b714601c0978725a19efc39f5f207b86d188ba01"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ec1f5a328464daf7a1e4e385e4f5652dd9b1d12405075ccba1df842f7774b4fc"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ad862295ad3f644e3c2c0d8b10a988e1600d3123ecb48702d2c0f26771f1c396"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48217be1de7d29a5600b5c513f3f7664b21d32e596d69582be0a94e36b8309cb"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e56afce6431450442f3ab5973156289bd5ec33dd618941283847c9fd5ff06bf"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:611068511b5531304137bcd7fe8117c985d1b828eb86043bd944cebb7fae3910"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b86abba762ecfeea359112b2bb4490802b340850bbee1948f785141a5e020de8"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-win32.whl", hash = "sha256:30d81cc1192dc693d49d5671cd40cdec596b885b0ce3b72f323888ab1c3863d5"}, - {file = "SQLAlchemy-2.0.27-cp38-cp38-win_amd64.whl", hash = "sha256:120af1e49d614d2525ac247f6123841589b029c318b9afbfc9e2b70e22e1827d"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d07ee7793f2aeb9b80ec8ceb96bc8cc08a2aec8a1b152da1955d64e4825fcbac"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb0845e934647232b6ff5150df37ceffd0b67b754b9fdbb095233deebcddbd4a"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fc19ae2e07a067663dd24fca55f8ed06a288384f0e6e3910420bf4b1270cc51"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b90053be91973a6fb6020a6e44382c97739736a5a9d74e08cc29b196639eb979"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2f5c9dfb0b9ab5e3a8a00249534bdd838d943ec4cfb9abe176a6c33408430230"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:33e8bde8fff203de50399b9039c4e14e42d4d227759155c21f8da4a47fc8053c"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-win32.whl", hash = "sha256:d873c21b356bfaf1589b89090a4011e6532582b3a8ea568a00e0c3aab09399dd"}, - {file = "SQLAlchemy-2.0.27-cp39-cp39-win_amd64.whl", hash = "sha256:ff2f1b7c963961d41403b650842dc2039175b906ab2093635d8319bef0b7d620"}, - {file = "SQLAlchemy-2.0.27-py3-none-any.whl", hash = "sha256:1ab4e0448018d01b142c916cc7119ca573803a4745cfe341b8f95657812700ac"}, - {file = "SQLAlchemy-2.0.27.tar.gz", hash = "sha256:86a6ed69a71fe6b88bf9331594fa390a2adda4a49b5c06f98e47bf0d392534f8"}, -] - -[package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} -typing-extensions = ">=4.6.0" - -[package.extras] -aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] -aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx_oracle (>=8)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] -pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3_binary"] - -[[package]] -name = "syrupy" -version = "4.6.1" -description = "Pytest Snapshot Test Utility" -optional = false -python-versions = ">=3.8.1,<4" -files = [ - {file = "syrupy-4.6.1-py3-none-any.whl", hash = "sha256:203e52f9cb9fa749cf683f29bd68f02c16c3bc7e7e5fe8f2fc59bdfe488ce133"}, - {file = "syrupy-4.6.1.tar.gz", hash = "sha256:37a835c9ce7857eeef86d62145885e10b3cb9615bc6abeb4ce404b3f18e1bb36"}, -] - -[package.dependencies] -pytest = ">=7.0.0,<9.0.0" - -[[package]] -name = "termcolor" -version = "2.4.0" -description = "ANSI color formatting for output in terminal" -optional = false -python-versions = ">=3.8" -files = [ - {file = "termcolor-2.4.0-py3-none-any.whl", hash = "sha256:9297c0df9c99445c2412e832e882a7884038a25617c60cea2ad69488d4040d63"}, - {file = "termcolor-2.4.0.tar.gz", hash = "sha256:aab9e56047c8ac41ed798fa36d892a37aca6b3e9159f3e0c24bc64a9b3ac7b7a"}, -] - -[package.extras] -tests = ["pytest", "pytest-cov"] - -[[package]] -name = "text-unidecode" -version = "1.3" -description = "The most basic Text::Unidecode port" -optional = false -python-versions = "*" -files = [ - {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, - {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, -] - -[[package]] -name = "tqdm" -version = "4.66.2" -description = "Fast, Extensible Progress Meter" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9"}, - {file = "tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] -notebook = ["ipywidgets (>=6)"] -slack = ["slack-sdk"] -telegram = ["requests"] - -[[package]] -name = "types-croniter" -version = "1.4.0.1" -description = "Typing stubs for croniter" -optional = false -python-versions = "*" -files = [ - {file = "types-croniter-1.4.0.1.tar.gz", hash = "sha256:5821688c93b874500b6863b8a164a8dbc49efdced985d00cbdddc029b81067b8"}, - {file = "types_croniter-1.4.0.1-py3-none-any.whl", hash = "sha256:b8996c11d5e942be59d669388e2515e50ca4935b61e4337bac9e81bfdde9cf72"}, -] - -[[package]] -name = "types-pytz" -version = "2023.3.1.1" -description = "Typing stubs for pytz" -optional = false -python-versions = "*" -files = [ - {file = "types-pytz-2023.3.1.1.tar.gz", hash = "sha256:cc23d0192cd49c8f6bba44ee0c81e4586a8f30204970fc0894d209a6b08dab9a"}, - {file = "types_pytz-2023.3.1.1-py3-none-any.whl", hash = "sha256:1999a123a3dc0e39a2ef6d19f3f8584211de9e6a77fe7a0259f04a524e90a5cf"}, -] - -[[package]] -name = "typing-extensions" -version = "4.10.0" -description = "Backported and Experimental Type Hints for Python 3.8+" -optional = false -python-versions = ">=3.8" -files = [ - {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, - {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, -] - -[[package]] -name = "ulid-transform" -version = "0.9.0" -description = "Create and transform ULIDs" -optional = false -python-versions = ">=3.10,<4.0" -files = [ - {file = "ulid_transform-0.9.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:4689679e3c42f23147ba9ff404e7fdb935ca1063d57971f8abb87e2c2deabfc5"}, - {file = "ulid_transform-0.9.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6123274882adc412f81f70f668dfee472588016679729a3e77627156d5a363f"}, - {file = "ulid_transform-0.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d954ebf7ef1c657d3d7e951bbbbf7f813b5514f66e49f9ae9065c23e718dfd48"}, - {file = "ulid_transform-0.9.0-cp310-cp310-manylinux_2_31_x86_64.whl", hash = "sha256:927e5afa53339399e22154692eaba39439afbe984b3a18c3deac36af57caddbe"}, - {file = "ulid_transform-0.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:ffbf01a1c6d7af77c76a53e6d7a2e08f0d648e74dbb2da7a1f8946a4c86876a3"}, - {file = "ulid_transform-0.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bcd90245996bf915b46b5d01fd3b0374f865f571d129eed41f0ea9c2ad4c4ba6"}, - {file = "ulid_transform-0.9.0-cp310-cp310-win32.whl", hash = "sha256:58761ba94eb339c1cfce11b8b287e6e7af8999d8e8bca26e40f76f3f8c8a65fc"}, - {file = "ulid_transform-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:0558c4dff9f5e319c9db16278bc0620f9589c896316bb945851e2dbdf8d83237"}, - {file = "ulid_transform-0.9.0-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:a5b9a22df12ef14838d17d0b336e4c583a74616dbd09ae7113836970e8839d57"}, - {file = "ulid_transform-0.9.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:775e444b4ab00a879278ab0d128cdacf782b860132e1ff46b481e6c0e2dbc67f"}, - {file = "ulid_transform-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b88908f7ea7f14664f214f3ef47cf89fbeb82f89523bd0a77a6b5614f79701d0"}, - {file = "ulid_transform-0.9.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d11c0ba06e56f0112546f53d7db340e922475503d1c98eb1f19b911162c71c16"}, - {file = "ulid_transform-0.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:31c7508a0702ece322002bfaa3a3a762d34f2e3575f1e944f0acb1640563bbb7"}, - {file = "ulid_transform-0.9.0-cp311-cp311-win32.whl", hash = "sha256:a7c2eec7fa3934e2735b437de698dc758a91f7c2b8d320b7b3a402016e058027"}, - {file = "ulid_transform-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:35d33a22bb2fd6bc96f563cb038ed59faadd69a839829117f9ce700d5671dbc7"}, - {file = "ulid_transform-0.9.0-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:cf6a9c9b582df4e6b39095fb1faa7632d1632350b197bb4a5a9e0dd4f77b6946"}, - {file = "ulid_transform-0.9.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30eed6d3f1162130992f64050990852751ead37ea415124a89eec5401306c9d3"}, - {file = "ulid_transform-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fc01628e64e8bb862487ee691101af01f9c06b108dc90e1f4812137265dae5f"}, - {file = "ulid_transform-0.9.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6b9c26f274a3f1d198abed31c6710695af06bba3aa836108cac640b545c81f3d"}, - {file = "ulid_transform-0.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9f41f78e3cdd86aa03211a191418133b856f1e9852c01bc8b1a93049b8637882"}, - {file = "ulid_transform-0.9.0-cp312-cp312-win32.whl", hash = "sha256:1294b33c347b23cbb9c80bb3fc0148a6cd2891c0fca3b0eedeed1e183f263080"}, - {file = "ulid_transform-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:6b5e7e2cb081b572b41d3f40b8b4401405b7f4ee1d4774ee9b1a0c6700f4bd1c"}, - {file = "ulid_transform-0.9.0-pp310-pypy310_pp73-macosx_11_0_x86_64.whl", hash = "sha256:492ec0f09af05f816b5d1dfa511d110f972cb37f64458b443f80cf5f67ed0256"}, - {file = "ulid_transform-0.9.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd7bf5157a8c3743984aa438e93e5ba221667bc357eabbe6ef4430eb103384c"}, - {file = "ulid_transform-0.9.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b08811281a66023693b1bbd8208d944f7200f6e5d7e429f7bd766c4734f395c4"}, - {file = "ulid_transform-0.9.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:deaa49436b87684d9a33f558fffe5fe9464de3ca2c41f331058a3c77a04223ec"}, - {file = "ulid_transform-0.9.0.tar.gz", hash = "sha256:e50c78ddb89629d1d7b5e7436b51bb49ac3ddca7a278a78640299d4cd49105bc"}, -] - -[[package]] -name = "urllib3" -version = "1.26.18" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" -files = [ - {file = "urllib3-1.26.18-py2.py3-none-any.whl", hash = "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07"}, - {file = "urllib3-1.26.18.tar.gz", hash = "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0"}, -] - -[package.extras] -brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] -secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "usb-devices" -version = "0.4.5" -description = "Tools for mapping, describing, and resetting USB devices" -optional = false -python-versions = ">=3.9,<4.0" -files = [ - {file = "usb_devices-0.4.5-py3-none-any.whl", hash = "sha256:8a415219ef1395e25aa0bddcad484c88edf9673acdeae8a07223ca7222a01dcf"}, - {file = "usb_devices-0.4.5.tar.gz", hash = "sha256:9b5c7606df2bc791c6c45b7f76244a0cbed83cb6fa4c68791a143c03345e195d"}, -] - -[[package]] -name = "virtualenv" -version = "20.25.0" -description = "Virtual Python Environment builder" -optional = false -python-versions = ">=3.7" -files = [ - {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, - {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, -] - -[package.dependencies] -distlib = ">=0.3.7,<1" -filelock = ">=3.12.2,<4" -platformdirs = ">=3.9.1,<5" - -[package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] - -[[package]] -name = "voluptuous" -version = "0.13.1" -description = "" -optional = false -python-versions = "*" -files = [ - {file = "voluptuous-0.13.1-py3-none-any.whl", hash = "sha256:4b838b185f5951f2d6e8752b68fcf18bd7a9c26ded8f143f92d6d28f3921a3e6"}, - {file = "voluptuous-0.13.1.tar.gz", hash = "sha256:e8d31c20601d6773cb14d4c0f42aee29c6821bbd1018039aac7ac5605b489723"}, -] - -[[package]] -name = "voluptuous-serialize" -version = "2.6.0" -description = "Convert voluptuous schemas to dictionaries" -optional = false -python-versions = "*" -files = [ - {file = "voluptuous-serialize-2.6.0.tar.gz", hash = "sha256:79acdc58239582a393144402d827fa8efd6df0f5350cdc606d9242f6f9bca7c4"}, - {file = "voluptuous_serialize-2.6.0-py3-none-any.whl", hash = "sha256:85a5c8d4d829cb49186c1b5396a8a517413cc5938e1bb0e374350190cd139616"}, -] - -[package.dependencies] -voluptuous = "*" - -[[package]] -name = "voluptuous-stubs" -version = "0.1.1" -description = "voluptuous stubs" -optional = false -python-versions = "*" -files = [ - {file = "voluptuous-stubs-0.1.1.tar.gz", hash = "sha256:70fb1c088242f20e11023252b5648cd77f831f692cd910c8f9713cc135cf8cc8"}, - {file = "voluptuous_stubs-0.1.1-py3-none-any.whl", hash = "sha256:f216c427ed7e190b8413e26cf4f67e1bda692ea8225ed0d875f7724d10b7cb10"}, -] - -[package.dependencies] -mypy = ">=0.720" -typing-extensions = ">=3.7.4" - -[[package]] -name = "winrt-runtime" -version = "2.0.0b1" -description = "Python projection of Windows Runtime (WinRT) APIs" -optional = false -python-versions = "<3.13,>=3.9" -files = [ - {file = "winrt-runtime-2.0.0b1.tar.gz", hash = "sha256:28db2ebe7bfb347d110224e9f23fe8079cea45af0fcbd643d039524ced07d22c"}, - {file = "winrt_runtime-2.0.0b1-cp310-cp310-win32.whl", hash = "sha256:8f812b01e2c8dd3ca68aa51a7aa02e815cc2ac3c8520a883b4ec7a4fc63afb04"}, - {file = "winrt_runtime-2.0.0b1-cp310-cp310-win_amd64.whl", hash = "sha256:f36f6102f9b7a08d917a6809117c085639b66be2c579f4089d3fd47b83e8f87b"}, - {file = "winrt_runtime-2.0.0b1-cp310-cp310-win_arm64.whl", hash = "sha256:4a99f267da96edc977623355b816b46c1344c66dc34732857084417d8cf9a96b"}, - {file = "winrt_runtime-2.0.0b1-cp311-cp311-win32.whl", hash = "sha256:ba998e3fc452338c5e2d7bf5174a6206580245066d60079ee4130082d0eb61c2"}, - {file = "winrt_runtime-2.0.0b1-cp311-cp311-win_amd64.whl", hash = "sha256:e7838f0fdf5653ce245888590214177a1f54884cece2c8dfbfe3d01b2780171e"}, - {file = "winrt_runtime-2.0.0b1-cp311-cp311-win_arm64.whl", hash = "sha256:2afa45b7385e99a63d55ccda29096e6a84fcd4c654479005c147b0e65e274abf"}, - {file = "winrt_runtime-2.0.0b1-cp312-cp312-win32.whl", hash = "sha256:edda124ff965cec3a6bfdb26fbe88e004f96975dd84115176e30c1efbcb16f4c"}, - {file = "winrt_runtime-2.0.0b1-cp312-cp312-win_amd64.whl", hash = "sha256:d8935951efeec6b3d546dce8f48bb203aface57a1ba991c066f0e12e84c8f91e"}, - {file = "winrt_runtime-2.0.0b1-cp312-cp312-win_arm64.whl", hash = "sha256:509fb9a03af5e1125433f58522725716ceef040050d33625460b5a5eb98a46ac"}, - {file = "winrt_runtime-2.0.0b1-cp39-cp39-win32.whl", hash = "sha256:41138fe4642345d7143e817ce0905d82e60b3832558143e0a17bfea8654c6512"}, - {file = "winrt_runtime-2.0.0b1-cp39-cp39-win_amd64.whl", hash = "sha256:081a429fe85c33cb6610c4a799184b7650b30f15ab1d89866f2bda246d3a5c0a"}, - {file = "winrt_runtime-2.0.0b1-cp39-cp39-win_arm64.whl", hash = "sha256:e6984604c6ae1f3258973ba2503d1ea5aa15e536ca41d6a131ad305ebbb6519d"}, -] - -[[package]] -name = "winrt-windows-devices-bluetooth" -version = "2.0.0b1" -description = "Python projection of Windows Runtime (WinRT) APIs" -optional = false -python-versions = "<3.13,>=3.9" -files = [ - {file = "winrt-Windows.Devices.Bluetooth-2.0.0b1.tar.gz", hash = "sha256:786bd43786b873a083b89debece538974f720584662a2573d6a8a8501a532860"}, - {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp310-cp310-win32.whl", hash = "sha256:79631bf3f96954da260859df9228a028835ffade0d885ba3942c5a86a853d150"}, - {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp310-cp310-win_amd64.whl", hash = "sha256:cd85337a95065d0d2045c06db1a5edd4a447aad47cf7027818f6fb69f831c56c"}, - {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp310-cp310-win_arm64.whl", hash = "sha256:6a963869ed003d260e90e9bedc334129303f263f068ea1c0d994df53317db2bc"}, - {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp311-cp311-win32.whl", hash = "sha256:7c5951943a3911d94a8da190f4355dc70128d7d7f696209316372c834b34d462"}, - {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp311-cp311-win_amd64.whl", hash = "sha256:b0bb154ae92235649ed234982f609c490a467d5049c27d63397be9abbb00730e"}, - {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp311-cp311-win_arm64.whl", hash = "sha256:6688dfb0fc3b7dc517bf8cf40ae00544a50b4dec91470d37be38fc33c4523632"}, - {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp312-cp312-win32.whl", hash = "sha256:613c6ff4125df46189b3bef6d3110d94ec725d357ab734f00eedb11c4116c367"}, - {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp312-cp312-win_amd64.whl", hash = "sha256:59c403b64e9f4e417599c6f6aea6ee6fac960597c21eac6b3fd8a84f64aa387c"}, - {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp312-cp312-win_arm64.whl", hash = "sha256:b7f6e1b9bb6e33be80045adebd252cf25cd648759fad6e86c61a393ddd709f7f"}, - {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp39-cp39-win32.whl", hash = "sha256:eae7a89106eab047e96843e28c3c6ce0886dd7dee60180a1010498925e9503f9"}, - {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp39-cp39-win_amd64.whl", hash = "sha256:8dfd1915c894ac19dd0b24aba38ef676c92c3473c0d9826762ba9616ad7df68b"}, - {file = "winrt_Windows.Devices.Bluetooth-2.0.0b1-cp39-cp39-win_arm64.whl", hash = "sha256:49058587e6d82ba33da0767b97a378ddfea8e3a5991bdeff680faa287bfae57e"}, -] - -[package.dependencies] -winrt-runtime = "2.0.0-beta.1" - -[package.extras] -all = ["winrt-Windows.Devices.Bluetooth.GenericAttributeProfile[all] (==2.0.0-beta.1)", "winrt-Windows.Devices.Bluetooth.Rfcomm[all] (==2.0.0-beta.1)", "winrt-Windows.Devices.Enumeration[all] (==2.0.0-beta.1)", "winrt-Windows.Devices.Radios[all] (==2.0.0-beta.1)", "winrt-Windows.Foundation.Collections[all] (==2.0.0-beta.1)", "winrt-Windows.Foundation[all] (==2.0.0-beta.1)", "winrt-Windows.Networking[all] (==2.0.0-beta.1)", "winrt-Windows.Storage.Streams[all] (==2.0.0-beta.1)"] - -[[package]] -name = "winrt-windows-devices-bluetooth-advertisement" -version = "2.0.0b1" -description = "Python projection of Windows Runtime (WinRT) APIs" -optional = false -python-versions = "<3.13,>=3.9" -files = [ - {file = "winrt-Windows.Devices.Bluetooth.Advertisement-2.0.0b1.tar.gz", hash = "sha256:d9050faa4377d410d4f0e9cabb5ec555a267531c9747370555ac9ec93ec9f399"}, - {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp310-cp310-win32.whl", hash = "sha256:ac9b703d16adc87c3541585525b8fcf6d84391e2fa010c2f001e714c405cc3b7"}, - {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp310-cp310-win_amd64.whl", hash = "sha256:593cade7853a8b0770e8ef30462b5d5f477b82e17e0aa590094b1c26efd3e05a"}, - {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp310-cp310-win_arm64.whl", hash = "sha256:574698c08895e2cfee7379bdf34a5f319fe440d7dfcc7bc9858f457c08e9712c"}, - {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp311-cp311-win32.whl", hash = "sha256:652a096f8210036bbb539d7f971eaf1f472a3aeb60b7e31278e3d0d30a355292"}, - {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp311-cp311-win_amd64.whl", hash = "sha256:e5cfb866c44dad644fb44b441f4fdbddafc9564075f1f68f756e20f438105c67"}, - {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp311-cp311-win_arm64.whl", hash = "sha256:6c2503eaaf5cd988b5510b86347dba45ad6ee52656f9656a1a97abae6d35386e"}, - {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp312-cp312-win32.whl", hash = "sha256:780c766725a55f4211f921c773c92c2331803e70f65d6ad6676a60f903d39a54"}, - {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp312-cp312-win_amd64.whl", hash = "sha256:39c8633d01039eb2c2f6f20cfc43c045a333b9f3a45229e2ce443f71bb2a562c"}, - {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp312-cp312-win_arm64.whl", hash = "sha256:eaa0d44b4158b16937eac8102249e792f0299dbb0aefc56cc9adc9552e8f9afe"}, - {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp39-cp39-win32.whl", hash = "sha256:d171487e23f7671ad2923544bfa6545d0a29a1a9ae1f5c1d5e5e5f473a5d62b2"}, - {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp39-cp39-win_amd64.whl", hash = "sha256:442eecac87653a03617e65bdb2ef79ddc0582dfdacc2be8af841fba541577f8b"}, - {file = "winrt_Windows.Devices.Bluetooth.Advertisement-2.0.0b1-cp39-cp39-win_arm64.whl", hash = "sha256:b30ab9b8c1ecf818be08bac86bee425ef40f75060c4011d4e6c2e624a7b9916e"}, -] - -[package.dependencies] -winrt-runtime = "2.0.0-beta.1" - -[package.extras] -all = ["winrt-Windows.Devices.Bluetooth[all] (==2.0.0-beta.1)", "winrt-Windows.Foundation.Collections[all] (==2.0.0-beta.1)", "winrt-Windows.Foundation[all] (==2.0.0-beta.1)", "winrt-Windows.Storage.Streams[all] (==2.0.0-beta.1)"] - -[[package]] -name = "winrt-windows-devices-bluetooth-genericattributeprofile" -version = "2.0.0b1" -description = "Python projection of Windows Runtime (WinRT) APIs" -optional = false -python-versions = "<3.13,>=3.9" -files = [ - {file = "winrt-Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1.tar.gz", hash = "sha256:93b745d51ecfb3e9d3a21623165cc065735c9e0146cb7a26744182c164e63e14"}, - {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp310-cp310-win32.whl", hash = "sha256:db740aaedd80cca5b1a390663b26c7733eb08f4c57ade6a04b055d548e9d042b"}, - {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp310-cp310-win_amd64.whl", hash = "sha256:7c81aa6c066cdab58bcc539731f208960e094a6d48b59118898e1e804dbbdf7f"}, - {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp310-cp310-win_arm64.whl", hash = "sha256:92277a6bbcbe2225ad1be92968af597dc77bc37a63cd729690d2d9fb5094ae25"}, - {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp311-cp311-win32.whl", hash = "sha256:6b48209669c1e214165530793cf9916ae44a0ae2618a9be7a489e8c94f7e745f"}, - {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp311-cp311-win_amd64.whl", hash = "sha256:2f17216e6ce748eaef02fb0658213515d3ff31e2dbb18f070a614876f818c90d"}, - {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp311-cp311-win_arm64.whl", hash = "sha256:db798a0f0762e390da5a9f02f822daff00692bd951a492224bf46782713b2938"}, - {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp312-cp312-win32.whl", hash = "sha256:b8d9dba04b9cfa53971c35117fc3c68c94bfa5e2ed18ce680f731743598bf246"}, - {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp312-cp312-win_amd64.whl", hash = "sha256:e5260b3f33dee8a896604297e05efc04d04298329c205a74ded8e2d6333e84b7"}, - {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp312-cp312-win_arm64.whl", hash = "sha256:822ef539389ecb546004345c4dce8b9b7788e2e99a1d6f0947a4b123dceb7fed"}, - {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp39-cp39-win32.whl", hash = "sha256:11e6863e7a94d2b6dd76ddcd19c01e311895810a4ce6ad08c7b5534294753243"}, - {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp39-cp39-win_amd64.whl", hash = "sha256:20de8d04c301c406362c93e78d41912aea0af23c4b430704aba329420d7c2cdf"}, - {file = "winrt_Windows.Devices.Bluetooth.GenericAttributeProfile-2.0.0b1-cp39-cp39-win_arm64.whl", hash = "sha256:918059796f2f123216163b928ecde8ecec17994fb7a94042af07fda82c132a6d"}, -] - -[package.dependencies] -winrt-runtime = "2.0.0-beta.1" - -[package.extras] -all = ["winrt-Windows.Devices.Bluetooth[all] (==2.0.0-beta.1)", "winrt-Windows.Devices.Enumeration[all] (==2.0.0-beta.1)", "winrt-Windows.Foundation.Collections[all] (==2.0.0-beta.1)", "winrt-Windows.Foundation[all] (==2.0.0-beta.1)", "winrt-Windows.Storage.Streams[all] (==2.0.0-beta.1)"] - -[[package]] -name = "winrt-windows-devices-enumeration" -version = "2.0.0b1" -description = "Python projection of Windows Runtime (WinRT) APIs" -optional = false -python-versions = "<3.13,>=3.9" -files = [ - {file = "winrt-Windows.Devices.Enumeration-2.0.0b1.tar.gz", hash = "sha256:8f214040e4edbe57c4943488887db89f4a00d028c34169aafd2205e228026100"}, - {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp310-cp310-win32.whl", hash = "sha256:dcb9e7d230aefec8531a46d393ecb1063b9d4b97c9f3ff2fc537ce22bdfa2444"}, - {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp310-cp310-win_amd64.whl", hash = "sha256:22a3e1fef40786cc8d51320b6f11ff25de6c674475f3ba608a46915e1dadf0f5"}, - {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp310-cp310-win_arm64.whl", hash = "sha256:2edcfeb70a71d40622873cad96982a28e92a7ee71f33968212dd3598b2d8d469"}, - {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp311-cp311-win32.whl", hash = "sha256:ce4eb88add7f5946d2666761a97a3bb04cac2a061d264f03229c1e15dbd7ce91"}, - {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp311-cp311-win_amd64.whl", hash = "sha256:a9001f17991572abdddab7ab074e08046e74e05eeeaf3b2b01b8b47d2879b64c"}, - {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp311-cp311-win_arm64.whl", hash = "sha256:0440b91ce144111e207f084cec6b1277162ef2df452d321951e989ce87dc9ced"}, - {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp312-cp312-win32.whl", hash = "sha256:e4fae13126f13a8d9420b74fb5a5ff6a6b2f91f7718c4be2d4a8dc1337c58f59"}, - {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp312-cp312-win_amd64.whl", hash = "sha256:e352eebc23dc94fb79e67a056c057fb0e16c20c8cb881dc826094c20ed4791e3"}, - {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp312-cp312-win_arm64.whl", hash = "sha256:b43f5c1f053a170e6e4b44ba69838ac223f9051adca1a56506d4c46e98d1485f"}, - {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp39-cp39-win32.whl", hash = "sha256:ed245fad8de6a134d5c3a630204e7f8238aa944a40388005bce0ce3718c410fa"}, - {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp39-cp39-win_amd64.whl", hash = "sha256:22a9eefdbfe520778512266d0b48ff239eaa8d272fce6f5cb1ff352bed0619f4"}, - {file = "winrt_Windows.Devices.Enumeration-2.0.0b1-cp39-cp39-win_arm64.whl", hash = "sha256:397d43f8fd2621a7719b9eab6a4a8e72a1d6fa2d9c36525a30812f8e7bad3bdf"}, -] - -[package.dependencies] -winrt-runtime = "2.0.0-beta.1" - -[package.extras] -all = ["winrt-Windows.ApplicationModel.Background[all] (==2.0.0-beta.1)", "winrt-Windows.Foundation.Collections[all] (==2.0.0-beta.1)", "winrt-Windows.Foundation[all] (==2.0.0-beta.1)", "winrt-Windows.Security.Credentials[all] (==2.0.0-beta.1)", "winrt-Windows.Storage.Streams[all] (==2.0.0-beta.1)", "winrt-Windows.UI.Popups[all] (==2.0.0-beta.1)", "winrt-Windows.UI[all] (==2.0.0-beta.1)"] - -[[package]] -name = "winrt-windows-foundation" -version = "2.0.0b1" -description = "Python projection of Windows Runtime (WinRT) APIs" -optional = false -python-versions = "<3.13,>=3.9" -files = [ - {file = "winrt-Windows.Foundation-2.0.0b1.tar.gz", hash = "sha256:976b6da942747a7ca5a179a35729d8dc163f833e03b085cf940332a5e9070d54"}, - {file = "winrt_Windows.Foundation-2.0.0b1-cp310-cp310-win32.whl", hash = "sha256:5337ac1ec260132fbff868603e73a3738d4001911226e72669b3d69c8a256d5e"}, - {file = "winrt_Windows.Foundation-2.0.0b1-cp310-cp310-win_amd64.whl", hash = "sha256:af969e5bb9e2e41e4e86a361802528eafb5eb8fe87ec1dba6048c0702d63caa8"}, - {file = "winrt_Windows.Foundation-2.0.0b1-cp310-cp310-win_arm64.whl", hash = "sha256:bbbfa6b3c444a1074a630fd4a1b71171be7a5c9bb07c827ad9259fadaed56cf2"}, - {file = "winrt_Windows.Foundation-2.0.0b1-cp311-cp311-win32.whl", hash = "sha256:b91bd92b1854c073acd81aa87cf8df571d2151b1dd050b6181aa36f7acc43df4"}, - {file = "winrt_Windows.Foundation-2.0.0b1-cp311-cp311-win_amd64.whl", hash = "sha256:2f5359f25703347e827dbac982150354069030f1deecd616f7ce37ad90cbcb00"}, - {file = "winrt_Windows.Foundation-2.0.0b1-cp311-cp311-win_arm64.whl", hash = "sha256:0f1f1978173ddf0ee6262c2edb458f62d628b9fa0df10cd1e8c78c833af3197e"}, - {file = "winrt_Windows.Foundation-2.0.0b1-cp312-cp312-win32.whl", hash = "sha256:c1d23b737f733104b91c89c507b58d0b3ef5f3234a1b608ef6dfb6dbbb8777ea"}, - {file = "winrt_Windows.Foundation-2.0.0b1-cp312-cp312-win_amd64.whl", hash = "sha256:95de6c29e9083fe63f127b965b54dfa52a6424a93a94ce87cfad4c1900a6e887"}, - {file = "winrt_Windows.Foundation-2.0.0b1-cp312-cp312-win_arm64.whl", hash = "sha256:4707063a5a6980e3f71aebeea5ac93101c753ec13a0b47be9ea4dbc0d5ff361e"}, - {file = "winrt_Windows.Foundation-2.0.0b1-cp39-cp39-win32.whl", hash = "sha256:d0259f1f4a1b8e20d0cbd935a889c0f7234f720645590260f9cf3850fdc1e1fa"}, - {file = "winrt_Windows.Foundation-2.0.0b1-cp39-cp39-win_amd64.whl", hash = "sha256:15c7b324d0f59839fb4492d84bb1c870881c5c67cb94ac24c664a7c4dce1c475"}, - {file = "winrt_Windows.Foundation-2.0.0b1-cp39-cp39-win_arm64.whl", hash = "sha256:16ad741f4d38e99f8409ba5760299d0052003255f970f49f4b8ba2e0b609c8b7"}, -] - -[package.dependencies] -winrt-runtime = "2.0.0-beta.1" - -[package.extras] -all = ["winrt-Windows.Foundation.Collections[all] (==2.0.0-beta.1)"] - -[[package]] -name = "winrt-windows-foundation-collections" -version = "2.0.0b1" -description = "Python projection of Windows Runtime (WinRT) APIs" -optional = false -python-versions = "<3.13,>=3.9" -files = [ - {file = "winrt-Windows.Foundation.Collections-2.0.0b1.tar.gz", hash = "sha256:185d30f8103934124544a40aac005fa5918a9a7cb3179f45e9863bb86e22ad43"}, - {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp310-cp310-win32.whl", hash = "sha256:042142e916a170778b7154498aae61254a1a94c552954266b73479479d24f01d"}, - {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp310-cp310-win_amd64.whl", hash = "sha256:9f68e66055121fc1e04c4fda627834aceee6fbe922e77d6ccaecf9582e714c57"}, - {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp310-cp310-win_arm64.whl", hash = "sha256:a4609411263cc7f5e93a9a5677b21e2ef130e26f9030bfa960b3e82595324298"}, - {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp311-cp311-win32.whl", hash = "sha256:5296858aa44c53936460a119794b80eedd6bd094016c1bf96822f92cb95ea419"}, - {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp311-cp311-win_amd64.whl", hash = "sha256:3db1e1c80c97474e7c88b6052bd8982ca61723fd58ace11dc91a5522662e0b2a"}, - {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp311-cp311-win_arm64.whl", hash = "sha256:c3a594e660c59f9fab04ae2f40bda7c809e8ec4748bada4424dfb02b43d4bfe1"}, - {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp312-cp312-win32.whl", hash = "sha256:0f355ee943ec5b835e694d97e9e93545a42d6fb984a61f442467789550d62c3f"}, - {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp312-cp312-win_amd64.whl", hash = "sha256:c4a0cd2eb9f47c7ca3b66d12341cc822250bf26854a93fd58ab77f7a48dfab3a"}, - {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp312-cp312-win_arm64.whl", hash = "sha256:744dbef50e8b8f34904083cae9ad43ac6e28facb9e166c4f123ce8e758141067"}, - {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp39-cp39-win32.whl", hash = "sha256:b7c767184aec3a3d7cba2cd84fadcd68106854efabef1a61092052294d6d6f4f"}, - {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp39-cp39-win_amd64.whl", hash = "sha256:7c1ffe99c12f14fc4ab7027757780e6d850fa2fb23ec404a54311fbd9f1970d3"}, - {file = "winrt_Windows.Foundation.Collections-2.0.0b1-cp39-cp39-win_arm64.whl", hash = "sha256:870fa040ed36066e4c240c35973d8b2e0d7c38cc6050a42d993715ec9e3b748c"}, -] - -[package.dependencies] -winrt-runtime = "2.0.0-beta.1" - -[package.extras] -all = ["winrt-Windows.Foundation[all] (==2.0.0-beta.1)"] - -[[package]] -name = "winrt-windows-storage-streams" -version = "2.0.0b1" -description = "Python projection of Windows Runtime (WinRT) APIs" -optional = false -python-versions = "<3.13,>=3.9" -files = [ - {file = "winrt-Windows.Storage.Streams-2.0.0b1.tar.gz", hash = "sha256:029d67cdc9b092d56c682740fe3c42f267dc5d3346b5c0b12ebc03f38e7d2f1f"}, - {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp310-cp310-win32.whl", hash = "sha256:49c90d4bfd539f6676226dfcb4b3574ddd6be528ffc44aa214c55af88c2de89e"}, - {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp310-cp310-win_amd64.whl", hash = "sha256:22cc82779cada84aa2633841e25b33f3357737d912a1d9ecc1ee5a8b799b5171"}, - {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp310-cp310-win_arm64.whl", hash = "sha256:b1750a111be32466f4f0781cbb5df195ac940690571dff4564492b921b162563"}, - {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp311-cp311-win32.whl", hash = "sha256:e79b1183ab26d9b95cf3e6dbe3f488a40605174a5a112694dbb7dbfb50899daf"}, - {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp311-cp311-win_amd64.whl", hash = "sha256:3e90a1207eb3076f051a7785132f7b056b37343a68e9481a50c6defb3f660099"}, - {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp311-cp311-win_arm64.whl", hash = "sha256:4da06522b4fa9cfcc046b604cc4aa1c6a887cc4bb5b8a637ed9bff8028a860bb"}, - {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp312-cp312-win32.whl", hash = "sha256:6f74f8ab8ac0d8de61c709043315361d8ac63f8144f3098d428472baadf8246a"}, - {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp312-cp312-win_amd64.whl", hash = "sha256:5cf7c8d67836c60392d167bfe4f98ac7abcb691bfba2d19e322d0f9181f58347"}, - {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp312-cp312-win_arm64.whl", hash = "sha256:f7f679f2c0f71791eca835856f57942ee5245094c1840a6c34bc7c2176b1bcd6"}, - {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp39-cp39-win32.whl", hash = "sha256:5beb53429fa9a11ede56b4a7cefe28c774b352dd355f7951f2a4dd7e9ec9b39a"}, - {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp39-cp39-win_amd64.whl", hash = "sha256:f84233c4b500279d8f5840cb8c47776bc040fcecba05c6c9ab9767053698fc8b"}, - {file = "winrt_Windows.Storage.Streams-2.0.0b1-cp39-cp39-win_arm64.whl", hash = "sha256:cfb163ddbb435906f75ef92a768573b0190e194e1438cea5a4c1d4d32a6b9386"}, -] - -[package.dependencies] -winrt-runtime = "2.0.0-beta.1" - -[package.extras] -all = ["winrt-Windows.Foundation.Collections[all] (==2.0.0-beta.1)", "winrt-Windows.Foundation[all] (==2.0.0-beta.1)", "winrt-Windows.Storage[all] (==2.0.0-beta.1)", "winrt-Windows.System[all] (==2.0.0-beta.1)"] - -[[package]] -name = "yarl" -version = "1.9.4" -description = "Yet another URL library" -optional = false -python-versions = ">=3.7" -files = [ - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a8c1df72eb746f4136fe9a2e72b0c9dc1da1cbd23b5372f94b5820ff8ae30e0e"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a3a6ed1d525bfb91b3fc9b690c5a21bb52de28c018530ad85093cc488bee2dd2"}, - {file = "yarl-1.9.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c38c9ddb6103ceae4e4498f9c08fac9b590c5c71b0370f98714768e22ac6fa66"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9e09c9d74f4566e905a0b8fa668c58109f7624db96a2171f21747abc7524234"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8477c1ee4bd47c57d49621a062121c3023609f7a13b8a46953eb6c9716ca392"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5ff2c858f5f6a42c2a8e751100f237c5e869cbde669a724f2062d4c4ef93551"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:357495293086c5b6d34ca9616a43d329317feab7917518bc97a08f9e55648455"}, - {file = "yarl-1.9.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54525ae423d7b7a8ee81ba189f131054defdb122cde31ff17477951464c1691c"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:801e9264d19643548651b9db361ce3287176671fb0117f96b5ac0ee1c3530d53"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e516dc8baf7b380e6c1c26792610230f37147bb754d6426462ab115a02944385"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:7d5aaac37d19b2904bb9dfe12cdb08c8443e7ba7d2852894ad448d4b8f442863"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:54beabb809ffcacbd9d28ac57b0db46e42a6e341a030293fb3185c409e626b8b"}, - {file = "yarl-1.9.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bac8d525a8dbc2a1507ec731d2867025d11ceadcb4dd421423a5d42c56818541"}, - {file = "yarl-1.9.4-cp310-cp310-win32.whl", hash = "sha256:7855426dfbddac81896b6e533ebefc0af2f132d4a47340cee6d22cac7190022d"}, - {file = "yarl-1.9.4-cp310-cp310-win_amd64.whl", hash = "sha256:848cd2a1df56ddbffeb375535fb62c9d1645dde33ca4d51341378b3f5954429b"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35a2b9396879ce32754bd457d31a51ff0a9d426fd9e0e3c33394bf4b9036b099"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c7d56b293cc071e82532f70adcbd8b61909eec973ae9d2d1f9b233f3d943f2c"}, - {file = "yarl-1.9.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8a1c6c0be645c745a081c192e747c5de06e944a0d21245f4cf7c05e457c36e0"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b3c1ffe10069f655ea2d731808e76e0f452fc6c749bea04781daf18e6039525"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:549d19c84c55d11687ddbd47eeb348a89df9cb30e1993f1b128f4685cd0ebbf8"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7409f968456111140c1c95301cadf071bd30a81cbd7ab829169fb9e3d72eae9"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e23a6d84d9d1738dbc6e38167776107e63307dfc8ad108e580548d1f2c587f42"}, - {file = "yarl-1.9.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8b889777de69897406c9fb0b76cdf2fd0f31267861ae7501d93003d55f54fbe"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:03caa9507d3d3c83bca08650678e25364e1843b484f19986a527630ca376ecce"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e9035df8d0880b2f1c7f5031f33f69e071dfe72ee9310cfc76f7b605958ceb9"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:c0ec0ed476f77db9fb29bca17f0a8fcc7bc97ad4c6c1d8959c507decb22e8572"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:ee04010f26d5102399bd17f8df8bc38dc7ccd7701dc77f4a68c5b8d733406958"}, - {file = "yarl-1.9.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49a180c2e0743d5d6e0b4d1a9e5f633c62eca3f8a86ba5dd3c471060e352ca98"}, - {file = "yarl-1.9.4-cp311-cp311-win32.whl", hash = "sha256:81eb57278deb6098a5b62e88ad8281b2ba09f2f1147c4767522353eaa6260b31"}, - {file = "yarl-1.9.4-cp311-cp311-win_amd64.whl", hash = "sha256:d1d2532b340b692880261c15aee4dc94dd22ca5d61b9db9a8a361953d36410b1"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0d2454f0aef65ea81037759be5ca9947539667eecebca092733b2eb43c965a81"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:44d8ffbb9c06e5a7f529f38f53eda23e50d1ed33c6c869e01481d3fafa6b8142"}, - {file = "yarl-1.9.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:aaaea1e536f98754a6e5c56091baa1b6ce2f2700cc4a00b0d49eca8dea471074"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3777ce5536d17989c91696db1d459574e9a9bd37660ea7ee4d3344579bb6f129"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fc5fc1eeb029757349ad26bbc5880557389a03fa6ada41703db5e068881e5f2"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea65804b5dc88dacd4a40279af0cdadcfe74b3e5b4c897aa0d81cf86927fee78"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa102d6d280a5455ad6a0f9e6d769989638718e938a6a0a2ff3f4a7ff8c62cc4"}, - {file = "yarl-1.9.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09efe4615ada057ba2d30df871d2f668af661e971dfeedf0c159927d48bbeff0"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:008d3e808d03ef28542372d01057fd09168419cdc8f848efe2804f894ae03e51"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6f5cb257bc2ec58f437da2b37a8cd48f666db96d47b8a3115c29f316313654ff"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:992f18e0ea248ee03b5a6e8b3b4738850ae7dbb172cc41c966462801cbf62cf7"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:0e9d124c191d5b881060a9e5060627694c3bdd1fe24c5eecc8d5d7d0eb6faabc"}, - {file = "yarl-1.9.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3986b6f41ad22988e53d5778f91855dc0399b043fc8946d4f2e68af22ee9ff10"}, - {file = "yarl-1.9.4-cp312-cp312-win32.whl", hash = "sha256:4b21516d181cd77ebd06ce160ef8cc2a5e9ad35fb1c5930882baff5ac865eee7"}, - {file = "yarl-1.9.4-cp312-cp312-win_amd64.whl", hash = "sha256:a9bd00dc3bc395a662900f33f74feb3e757429e545d831eef5bb280252631984"}, - {file = "yarl-1.9.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:63b20738b5aac74e239622d2fe30df4fca4942a86e31bf47a81a0e94c14df94f"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d7f7de27b8944f1fee2c26a88b4dabc2409d2fea7a9ed3df79b67277644e17"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c74018551e31269d56fab81a728f683667e7c28c04e807ba08f8c9e3bba32f14"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca06675212f94e7a610e85ca36948bb8fc023e458dd6c63ef71abfd482481aa5"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aef935237d60a51a62b86249839b51345f47564208c6ee615ed2a40878dccdd"}, - {file = "yarl-1.9.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b134fd795e2322b7684155b7855cc99409d10b2e408056db2b93b51a52accc7"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d25039a474c4c72a5ad4b52495056f843a7ff07b632c1b92ea9043a3d9950f6e"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f7d6b36dd2e029b6bcb8a13cf19664c7b8e19ab3a58e0fefbb5b8461447ed5ec"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:957b4774373cf6f709359e5c8c4a0af9f6d7875db657adb0feaf8d6cb3c3964c"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d7eeb6d22331e2fd42fce928a81c697c9ee2d51400bd1a28803965883e13cead"}, - {file = "yarl-1.9.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6a962e04b8f91f8c4e5917e518d17958e3bdee71fd1d8b88cdce74dd0ebbf434"}, - {file = "yarl-1.9.4-cp37-cp37m-win32.whl", hash = "sha256:f3bc6af6e2b8f92eced34ef6a96ffb248e863af20ef4fde9448cc8c9b858b749"}, - {file = "yarl-1.9.4-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4d7a90a92e528aadf4965d685c17dacff3df282db1121136c382dc0b6014d2"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ec61d826d80fc293ed46c9dd26995921e3a82146feacd952ef0757236fc137be"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8be9e837ea9113676e5754b43b940b50cce76d9ed7d2461df1af39a8ee674d9f"}, - {file = "yarl-1.9.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bef596fdaa8f26e3d66af846bbe77057237cb6e8efff8cd7cc8dff9a62278bbf"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d47552b6e52c3319fede1b60b3de120fe83bde9b7bddad11a69fb0af7db32f1"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fc30f71689d7fc9168b92788abc977dc8cefa806909565fc2951d02f6b7d57"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4aa9741085f635934f3a2583e16fcf62ba835719a8b2b28fb2917bb0537c1dfa"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:206a55215e6d05dbc6c98ce598a59e6fbd0c493e2de4ea6cc2f4934d5a18d130"}, - {file = "yarl-1.9.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07574b007ee20e5c375a8fe4a0789fad26db905f9813be0f9fef5a68080de559"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5a2e2433eb9344a163aced6a5f6c9222c0786e5a9e9cac2c89f0b28433f56e23"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6ad6d10ed9b67a382b45f29ea028f92d25bc0bc1daf6c5b801b90b5aa70fb9ec"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6fe79f998a4052d79e1c30eeb7d6c1c1056ad33300f682465e1b4e9b5a188b78"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a825ec844298c791fd28ed14ed1bffc56a98d15b8c58a20e0e08c1f5f2bea1be"}, - {file = "yarl-1.9.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8619d6915b3b0b34420cf9b2bb6d81ef59d984cb0fde7544e9ece32b4b3043c3"}, - {file = "yarl-1.9.4-cp38-cp38-win32.whl", hash = "sha256:686a0c2f85f83463272ddffd4deb5e591c98aac1897d65e92319f729c320eece"}, - {file = "yarl-1.9.4-cp38-cp38-win_amd64.whl", hash = "sha256:a00862fb23195b6b8322f7d781b0dc1d82cb3bcac346d1e38689370cc1cc398b"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:604f31d97fa493083ea21bd9b92c419012531c4e17ea6da0f65cacdcf5d0bd27"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a854227cf581330ffa2c4824d96e52ee621dd571078a252c25e3a3b3d94a1b1"}, - {file = "yarl-1.9.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba6f52cbc7809cd8d74604cce9c14868306ae4aa0282016b641c661f981a6e91"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6327976c7c2f4ee6816eff196e25385ccc02cb81427952414a64811037bbc8b"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8397a3817d7dcdd14bb266283cd1d6fc7264a48c186b986f32e86d86d35fbac5"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0381b4ce23ff92f8170080c97678040fc5b08da85e9e292292aba67fdac6c34"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23d32a2594cb5d565d358a92e151315d1b2268bc10f4610d098f96b147370136"}, - {file = "yarl-1.9.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddb2a5c08a4eaaba605340fdee8fc08e406c56617566d9643ad8bf6852778fc7"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26a1dc6285e03f3cc9e839a2da83bcbf31dcb0d004c72d0730e755b33466c30e"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:18580f672e44ce1238b82f7fb87d727c4a131f3a9d33a5e0e82b793362bf18b4"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:29e0f83f37610f173eb7e7b5562dd71467993495e568e708d99e9d1944f561ec"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:1f23e4fe1e8794f74b6027d7cf19dc25f8b63af1483d91d595d4a07eca1fb26c"}, - {file = "yarl-1.9.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:db8e58b9d79200c76956cefd14d5c90af54416ff5353c5bfd7cbe58818e26ef0"}, - {file = "yarl-1.9.4-cp39-cp39-win32.whl", hash = "sha256:c7224cab95645c7ab53791022ae77a4509472613e839dab722a72abe5a684575"}, - {file = "yarl-1.9.4-cp39-cp39-win_amd64.whl", hash = "sha256:824d6c50492add5da9374875ce72db7a0733b29c2394890aef23d533106e2b15"}, - {file = "yarl-1.9.4-py3-none-any.whl", hash = "sha256:928cecb0ef9d5a7946eb6ff58417ad2fe9375762382f1bf5c55e61645f2c43ad"}, - {file = "yarl-1.9.4.tar.gz", hash = "sha256:566db86717cf8080b99b58b083b773a908ae40f06681e87e589a976faf8246bf"}, -] - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" - -[[package]] -name = "zlib-ng" -version = "0.4.0" -description = "Drop-in replacement for zlib and gzip modules using zlib-ng" -optional = false -python-versions = ">=3.8" -files = [ - {file = "zlib-ng-0.4.0.tar.gz", hash = "sha256:3b730881aaeb86f9a4995de5e22499406ccf92f8508b5c017c343d27570a8c0a"}, - {file = "zlib_ng-0.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b53458b4baa0554df93430bfda71a6861510d6641ac75192e6b9c2485d01a3a"}, - {file = "zlib_ng-0.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ac2405b42420abd9dccfe5d5f05c052aaf88ee66aec0d3fb4ee171826846d8a"}, - {file = "zlib_ng-0.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70e192695fd7bac2c3db529a873f57e10a8d42383223b0c5dc281793be4b1b83"}, - {file = "zlib_ng-0.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9b49346d528da8e13575bb8bfa2ee5f74398422e81d4be6001afb5c0621dc412"}, - {file = "zlib_ng-0.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:55a47ce2249581bc00decc5fc4aadf1f48c5edde770ff5aa649c2f0b782c9aba"}, - {file = "zlib_ng-0.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ec04f0a21e711a654a9ea1dc5966c29231301625cfc199ca1ec0cdedbf921377"}, - {file = "zlib_ng-0.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:096d319b94454c174dde78886a8d4a0f488186a4fbd006bd1819360e0e8b5348"}, - {file = "zlib_ng-0.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07694a956028a66133c52ebf802d6185c6e986fbec5c4e403b997e044b30db8d"}, - {file = "zlib_ng-0.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd4c9d4945f366a0f295e9356dd9ef291544adbe42cabcc121a28b202dd8809b"}, - {file = "zlib_ng-0.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:39f69f92c7f8d107f406d981c1383c749894d737699116138de14497b0e0b041"}, - {file = "zlib_ng-0.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6db621bdd34ef500ec1b44a5190fe5e967eee9386140be6bc8769ec15e355c4b"}, - {file = "zlib_ng-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:8b2a635d018b3dbed6844ceca08c0f9a170ebdcc9299ab080e4f63b757faaeae"}, - {file = "zlib_ng-0.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:274da13e42dc2197f8c24e321cc9cc4d1eef790512485462d72832343fe8f72a"}, - {file = "zlib_ng-0.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b87aba7e64de1efb5a965d51551b63efc24d9cc77671b7ea28f336162edc733"}, - {file = "zlib_ng-0.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e926e2f8a31a3bfd0e1e2ffc3fb9956126ee17b4477ee98aa4e51b7bdc7ad41d"}, - {file = "zlib_ng-0.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba917d5e574ae67b3984835791b5887592d0cb2877d5bfe22c3ab7ef30a28979"}, - {file = "zlib_ng-0.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1e122967cacce2f1f04b5ee1ea89642997f8a312be6b527dc4a8e92deb834dd"}, - {file = "zlib_ng-0.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:57c53634157142b208b6dd4dbb21b6b67392afb7b181be0e97a09bfc7201819b"}, - {file = "zlib_ng-0.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8faa04e00261afd0e532392f70e74428687d00a37b6c3e63e6eb27ad8a81a629"}, - {file = "zlib_ng-0.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7dca9af5f84edcc98408af60c4fd220fe2ba3f6e7324b6b97483ac430e1ba89b"}, - {file = "zlib_ng-0.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f338be6e62e05636467b89c26e0404d0e3d726da74aa3510be1e19e7681832b"}, - {file = "zlib_ng-0.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4edeb933440e94d2c47331314edfda20f51a3640eb8e12a7a478859874d35a4a"}, - {file = "zlib_ng-0.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:676221d0abb724d97a1b693b99b63fe164b65cd419c31556f5bf538f5a950031"}, - {file = "zlib_ng-0.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:08a52fb23236870b956d02400a372f1c3a8adef298552466b6476a05ba061027"}, - {file = "zlib_ng-0.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:690d1ee223a75c61bb628b7203d06d08ea4e10e88c822a4fe4fa8bdad0955608"}, - {file = "zlib_ng-0.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b53d72a2787ad5170a1c4b2444f14f064017bdc575ac43547054fdf0e8f8c4e"}, - {file = "zlib_ng-0.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67d37a39ed6521dfc31230f78ba095141d2317ad41bed9270eddfd1a37b9f076"}, - {file = "zlib_ng-0.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:209a4a2bb0797598f49aa7b7a9e8714b9f69a64777957eba476209d26bfec17b"}, - {file = "zlib_ng-0.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b585e8ddd357fe4677c0c738e5962ca867e157257f3c33f2fa8965e04bdb5836"}, - {file = "zlib_ng-0.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:4a05cafa6a15284406a8c92eed06faa439dfd26b6c9c697719be450b919b459d"}, - {file = "zlib_ng-0.4.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d62b1eaee0ae8fd6f544e199b4de90c018deaf1572f5e0c67ea5eb1adac7bfd3"}, - {file = "zlib_ng-0.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ec74451bf33678a77ebbb9f24364390469396d6a1be69114063343dc654167"}, - {file = "zlib_ng-0.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ae20fde7487931146ea1d95b5ea524012c2b20d26d8a8458bf6befff1feaf1b"}, - {file = "zlib_ng-0.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ab9cf1fca3128da63a1b67490fb4c753b6a880b183d826d49d4cd0c61951d0fa"}, - {file = "zlib_ng-0.4.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a3731a1e44290a2ca568690b8c6b62994377d929fd9b2808e60ea371f21781f4"}, - {file = "zlib_ng-0.4.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8430bbbca7689ce87735970bc3b9dcb1b6d1453aa6c01f5a6850a7c323e100c4"}, - {file = "zlib_ng-0.4.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9480339df63dc83dbe17ed01ac2fcac2e1e3fcf95811d86f9118823b6434ac58"}, - {file = "zlib_ng-0.4.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:1d4df3e5d779ded9451c3e14686233d0a75762512f2eaa74386eadf8fbb2850d"}, - {file = "zlib_ng-0.4.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6cf9e47962c86973543fd5c8fe46148063950fbb591da5775cde54abf6aa95aa"}, - {file = "zlib_ng-0.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0261b43542d1525dfd3475a9e083b624b61dfc5b7fec2a3a7be5908af867fd1b"}, - {file = "zlib_ng-0.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fd8a3cd1c108e714b08263a9b62d85d2bb1ba91ede319267ed998b6ac73bac8"}, - {file = "zlib_ng-0.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:959f8951cb7a44df190cbd69327c3ea467b6d6398c448727ecdbd520b6c4ba14"}, -] - -[metadata] -lock-version = "2.0" -python-versions = ">=3.11, <3.13" -content-hash = "47de1a8dbed2a861e3e886fe34ed0636f065101d890ed6609f54b0f5fb5399dc" diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 530f011..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,80 +0,0 @@ -[tool.pytest.ini_options] -testpaths = "tests" -norecursedirs = ".git" -asyncio_mode = "auto" - -[tool.isort] -profile = "black" -multi_line_output = 3 - -[tool.mypy] -python_version = "3.11" -show_error_codes = true -follow_imports = "silent" -local_partial_types = true -strict_equality = true -no_implicit_optional = true -warn_incomplete_stub = true -warn_redundant_casts = true -warn_unused_configs = true -warn_unused_ignores = true -check_untyped_defs = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_decorators = true -disallow_untyped_defs = true -warn_return_any = true -warn_unreachable = true - -[[tool.mypy.overrides]] -module = "homeassistant.components.*" -ignore_missing_imports = true - -[tool.coverage.paths] -source = ["custom_components/audiobookshelf", "utils", "tests"] - -[tool.ruff] -select = ["E", "W", "F", "B", "C", "I", "N", "UP", "ANN", "BLE", "A", "COM", "C4", "G", "ISC", "NPY", "INP", "PIE", "T20", "PYI", "Q", "RET", "SLF", "SIM", "TID", "RUF"] -ignore = ["ANN101"] -line-length = 150 -target-version = "py310" -exclude = ["utils/measure/playground", "utils/visualize", ".github", "docs"] - -[tool.ruff.per-file-ignores] -"utils/measure/**.py" = ["T201"] - -[tool.ruff.mccabe] -max-complexity = 15 - -[tool.poetry] -name = "audiobookshelf" -version = "v0.1.1" -description = "Audiobookshelf HA custom component" -authors = ["wolffshots <16850875+wolffshots@users.noreply.github.com>"] -readme = "README.md" -package-mode = false - -[tool.poetry.group.dev.dependencies] -pre-commit = "^3.3" -ruff = "^0.0" -voluptuous-stubs = "^0.1" -homeassistant-stubs = "^2024.1.2" -numpy = "^1.23" -black = "^24.3" -freezegun = "^1.2" -pytest-homeassistant-custom-component = "^0.13" -types-pytz = "^2023.3" -types-croniter = "^1.3" -croniter = "^1.4" -pytest-cov = "^4.0" -coverage = "^7.2" - -[tool.poetry.dependencies] -python = ">=3.11, <3.13" -asynctest = "^0.13.0" -homeassistant = "^2024.2.5" - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f5baf5a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +colorlog==6.9.0 +homeassistant==2024.11.0 +pip>=21.3.1 +ruff==0.7.2 \ No newline at end of file diff --git a/scripts/develop b/scripts/develop new file mode 100755 index 0000000..89eda50 --- /dev/null +++ b/scripts/develop @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +# Create config dir if not present +if [[ ! -d "${PWD}/config" ]]; then + mkdir -p "${PWD}/config" + hass --config "${PWD}/config" --script ensure_config +fi + +# Set the path to custom_components +## This let's us have the structure we want /custom_components/integration_blueprint +## while at the same time have Home Assistant configuration inside /config +## without resulting to symlinks. +export PYTHONPATH="${PYTHONPATH}:${PWD}/custom_components" + +# Start Home Assistant +hass --config "${PWD}/config" --debug diff --git a/scripts/lint b/scripts/lint new file mode 100755 index 0000000..5d68d15 --- /dev/null +++ b/scripts/lint @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +ruff format . +ruff check . --fix diff --git a/scripts/setup b/scripts/setup new file mode 100755 index 0000000..141d19f --- /dev/null +++ b/scripts/setup @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +python3 -m pip install --requirement requirements.txt From a06da307632cc69ce1d8318bdb354058f31d497c Mon Sep 17 00:00:00 2001 From: jadon <16850875+wolffshots@users.noreply.github.com> Date: Wed, 20 Nov 2024 18:51:01 +0000 Subject: [PATCH 15/20] feat: rewrite library sensors and config flow --- .devcontainer.json | 2 +- .gitignore | 6 +- .pre-commit-config.yaml | 5 - .ruff.toml | 1 + custom_components/audiobookshelf/__init__.py | 58 +-- .../audiobookshelf/config_flow.py | 46 ++ custom_components/audiobookshelf/const.py | 6 + .../audiobookshelf/manifest.json | 11 +- custom_components/audiobookshelf/sensor.py | 417 ++++++++++++------ hacs.json | 2 +- requirements.txt | 6 +- scripts/setup | 6 + 12 files changed, 394 insertions(+), 172 deletions(-) delete mode 100644 .pre-commit-config.yaml create mode 100644 custom_components/audiobookshelf/config_flow.py create mode 100644 custom_components/audiobookshelf/const.py diff --git a/.devcontainer.json b/.devcontainer.json index 9a91ab6..d91379b 100644 --- a/.devcontainer.json +++ b/.devcontainer.json @@ -1,5 +1,5 @@ { - "name": "ludeeus/integration_blueprint", + "name": "wolffshots/hass-audiobookshelf", "image": "mcr.microsoft.com/devcontainers/python:3.12", "postCreateCommand": "scripts/setup", "forwardPorts": [ diff --git a/.gitignore b/.gitignore index 765e507..e47ae6f 100644 --- a/.gitignore +++ b/.gitignore @@ -163,4 +163,8 @@ cython_debug/ test -test_creds.py \ No newline at end of file +test_creds.py + +# Home Assistant configuration +config/* +!config/configuration.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml deleted file mode 100644 index af7b58a..0000000 --- a/.pre-commit-config.yaml +++ /dev/null @@ -1,5 +0,0 @@ -repos: - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: 'v0.0.265' - hooks: - - id: ruff diff --git a/.ruff.toml b/.ruff.toml index 8ea6a71..3dfe628 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -14,6 +14,7 @@ ignore = [ "D212", # multi-line-summary-first-line (incompatible with formatter) "COM812", # incompatible with formatter "ISC001", # incompatible with formatter + "ERA001", commented code isn't always bad ] [lint.flake8-pytest-style] diff --git a/custom_components/audiobookshelf/__init__.py b/custom_components/audiobookshelf/__init__.py index e2f10bc..59d93f2 100644 --- a/custom_components/audiobookshelf/__init__.py +++ b/custom_components/audiobookshelf/__init__.py @@ -1,17 +1,23 @@ """Custom component for Audiobookshelf.""" + import logging + import voluptuous as vol -from homeassistant.helpers import config_validation as cv, discovery +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_API_KEY, CONF_SCAN_INTERVAL, CONF_URL +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import config_validation as cv -DOMAIN = "audiobookshelf" +from .const import DOMAIN, PLATFORMS CONFIG_SCHEMA = vol.Schema( { DOMAIN: vol.Schema( { - vol.Required("api_key"): cv.string, - vol.Required("api_url"): cv.string, - vol.Optional("scan_interval", default=300): cv.positive_int, + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_URL): cv.string, + vol.Optional(CONF_SCAN_INTERVAL, default=300): cv.positive_int, } ) }, @@ -21,27 +27,27 @@ _LOGGER = logging.getLogger(__name__) -async def async_setup(hass, config): - """Set up the Audiobookshelf component.""" - conf = config.get(DOMAIN) - if conf is None: - _LOGGER.error(f"No config found for {DOMAIN}!") - return True - api_key = conf["api_key"] - api_url = conf["api_url"] - scan_interval = conf["scan_interval"] - - _LOGGER.info("API URL: %s", api_url) - _LOGGER.info("Scan Interval: %s", scan_interval) - - hass.data[DOMAIN] = { - "api_key": api_key, - "api_url": api_url, - "scan_interval": scan_interval, - } - # Schedule the setup of sensor platform if needed - hass.async_create_task( - discovery.async_load_platform(hass, "sensor", DOMAIN, {}, config) +def clean_config(data: dict) -> dict: + """Remove the api key from config.""" + if bool(data[CONF_API_KEY]): + data[CONF_API_KEY] = "" + return data + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Audiobookshelf from a config entry.""" + if entry.data is None: + error_message = "Configuration not found" + raise ConfigEntryNotReady(error_message) + + _LOGGER.debug( + "Setting up Audiobookshelf with config: %s", clean_config(entry.data.copy()) ) + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/custom_components/audiobookshelf/config_flow.py b/custom_components/audiobookshelf/config_flow.py new file mode 100644 index 0000000..7f44367 --- /dev/null +++ b/custom_components/audiobookshelf/config_flow.py @@ -0,0 +1,46 @@ +"""Config flow for Audiobookshelf integration.""" + +from __future__ import annotations + +import homeassistant.helpers.config_validation as cv +import voluptuous as vol +from homeassistant import config_entries +from homeassistant.const import CONF_API_KEY, CONF_SCAN_INTERVAL, CONF_URL + +from .const import DOMAIN + + +class AudiobookshelfConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Audiobookshelf.""" + + VERSION = 1 + + async def async_step_user( + self, user_input: dict | None = None + ) -> config_entries.ConfigFlowResult: + """Handle the user step.""" + errors = {} + + if user_input is not None: + # Process user input + return self.async_create_entry( + title="Audiobookshelf", + data={ + CONF_URL: user_input[CONF_URL], + CONF_API_KEY: user_input[CONF_API_KEY], + CONF_SCAN_INTERVAL: user_input.get(CONF_SCAN_INTERVAL, 300), + }, + ) + + # Show the form to the user + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_URL): str, + vol.Required(CONF_API_KEY): str, + vol.Optional(CONF_SCAN_INTERVAL, default=300): cv.positive_int, + } + ), + errors=errors, + ) diff --git a/custom_components/audiobookshelf/const.py b/custom_components/audiobookshelf/const.py new file mode 100644 index 0000000..da2f91d --- /dev/null +++ b/custom_components/audiobookshelf/const.py @@ -0,0 +1,6 @@ +"""Constants for the Audiobookshelf integration.""" + +from homeassistant.const import Platform + +DOMAIN = "audiobookshelf" +PLATFORMS: list[Platform] = [Platform.SENSOR] diff --git a/custom_components/audiobookshelf/manifest.json b/custom_components/audiobookshelf/manifest.json index e94228b..4d945cc 100644 --- a/custom_components/audiobookshelf/manifest.json +++ b/custom_components/audiobookshelf/manifest.json @@ -6,7 +6,12 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/wolffshots/hass-audiobookshelf/issues", "dependencies": [], - "requirements": ["aiohttp"], - "codeowners": ["@wolffshots"], - "integration_type": "device" + "requirements": [ + "aiohttp" + ], + "codeowners": [ + "@wolffshots" + ], + "integration_type": "device", + "config_flow": true } \ No newline at end of file diff --git a/custom_components/audiobookshelf/sensor.py b/custom_components/audiobookshelf/sensor.py index 23bc938..282fae5 100644 --- a/custom_components/audiobookshelf/sensor.py +++ b/custom_components/audiobookshelf/sensor.py @@ -1,180 +1,246 @@ +"""Module containing the sensor platform for the Audiobookshelf integration.""" -import asyncio import logging -from typing import Any -import aiohttp +from dataclasses import dataclass from datetime import timedelta -from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry, EntityRegistry +from typing import Any, TypeVar +import aiohttp +from dacite import Config, from_dict +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_API_KEY, CONF_SCAN_INTERVAL, CONF_URL +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import ( DataUpdateCoordinator, UpdateFailed, ) -from homeassistant.core import HomeAssistant + +from custom_components.audiobookshelf import clean_config _LOGGER = logging.getLogger(__name__) DOMAIN = "audiobookshelf" +HTTP_OK = 200 + -async def count_active_users(data: dict) -> int: - """ - Takes in an object with an array of users - and counts the active ones minus - the dummy hass one - """ +def count_active_users(data: dict) -> int: + """Take in an object with an array of users and counts the active ones.""" count = 0 for user in data["users"]: if user["isActive"] and user["username"] != "hass": - if ("token" in user and user["token"] == API_KEY): + if "token" in user and user["token"] == API_KEY: continue # Skip user with provided access_token count += 1 return count -async def clean_user_attributes(data: dict): - """ - Removes the token and some extra data from users - """ + +def clean_user_attributes(data: dict) -> dict: + """Remove the token and some extra data from users.""" for user in data["users"]: - user["token"] = "" + user["token"] = "" # noqa: S105 return data -async def count_open_sessions(data: dict) -> int: - """ - Counts the number of open stream sessions - """ + +def count_open_sessions(data: dict) -> int: + """Count the number of open stream sessions.""" return len(data["openSessions"]) -async def count_libraries(data: dict) -> int: - """ - Counts the number libraries - """ + +def count_libraries(data: dict) -> int: + """Count the number libraries.""" return len(data["libraries"]) -async def extract_library_details(data: dict) -> dict: + +def extract_library_details(data: dict) -> dict: + """Extract the details from the library.""" details = {} - for library in data.get('libraries', []): - details.update({library['id']: {"mediaType": library['mediaType'],"provider": library['provider']}}) + for library in data.get("libraries", []): + details.update( + { + library["id"]: { + "mediaType": library["mediaType"], + "provider": library["provider"], + } + } + ) return details -def get_total_duration(total_duration: float): + +def get_total_duration(total_duration: float | None) -> float | None: """Calculate the total duration in hours and round it to 0 decimal places.""" + if total_duration is None: + return None return round(total_duration / 60.0 / 60.0, 0) -def get_total_size(total_size: float): - return round(total_size / 1024.0 / 1024.0 / 1024.0, 2) -async def fetch_library_stats(session, id): - """Fetch data from a single endpoint.""" - headers = {"Authorization": f"Bearer {API_KEY}"} - endpoint = f"api/libraries/{id}/stats" - try: - async with session.get(f"{API_URL}/{endpoint}", headers=headers) as response: - if response.status != 200: - _LOGGER.error(f"Failed to fetch data from {endpoint}, status: {response.status}") - return None - return await response.json() - except Exception as e: - _LOGGER.error(f"Exception occurred while fetching data from {endpoint}: {e}") +def get_total_size(total_size: float | None) -> float | None: + """Convert the size to human readable.""" + if total_size is None: return None + return round(total_size / 1024.0 / 1024.0 / 1024.0, 2) -async def get_library_stats(data: dict) -> dict: - library_details = await extract_library_details(data) - async with aiohttp.ClientSession() as session: - results = {} - for id in library_details: - library_stats = await fetch_library_stats(session, id) - if isinstance(library_stats, Exception): - _LOGGER.error(f"Error fetching data: {library_stats}") - else: - # response for a decent library will be HUGE if we don't pick and choose bits - summary = {} - if library_details[id]["mediaType"] == "book": - summary.update({"totalAuthors":library_stats["totalAuthors"]}) - if library_stats["totalAuthors"] is not None: - summary.update({"totalAuthors":library_stats["totalAuthors"]}) - else: - summary.update({"totalAuthors": "0"}) - elif library_details[id]["mediaType"] == "podcast": - if library_stats["numAudioTracks"] is not None: - summary.update({"numAudioTracks":library_stats["numAudioTracks"]}) - else: - summary.update({"numAudioTracks": "0"}) - - if library_stats["totalItems"] is not None: - summary.update({"totalItems":library_stats["totalItems"]}) - else: - summary.update({"totalItems": "0"}) - - if library_stats["totalSize"] is not None: - summary.update({"totalSize": f"{get_total_size(library_stats["totalSize"])}GB"}) - else: - summary.update({"totalSize": "0 GB"}) - - if library_stats["totalDuration"] is not None: - summary.update({"totalDuration": f"{get_total_duration(library_stats["totalDuration"])} hours"}) - else: - summary.update({"totalDuration": "0 hours"}) - - results.update({id: summary}) - return results - -async def do_nothing(data): + +def get_library_stats(data: dict) -> dict: + """Get statistics for each library.""" + return extract_library_details(data) + + +def do_nothing(data: dict) -> dict: + """Return the input data without any modifications.""" return data + type Sensor = dict[str, Any] # simple polling sensors sensors: dict[str, Sensor] = { "users": { - "endpoint": "api/users", - "name": "Audiobookshelf Users", + "endpoint": "api/users", + "name": "Audiobookshelf Users", "data_function": count_active_users, - "attributes_function": clean_user_attributes + "attributes_function": clean_user_attributes, + "unit": "users", }, "sessions": { - "endpoint": "api/users/online", - "name": "Audiobookshelf Open Sessions", + "endpoint": "api/users/online", + "name": "Audiobookshelf Open Sessions", "data_function": count_open_sessions, - "attributes_function": do_nothing + "attributes_function": do_nothing, + "unit": "sessions", }, "libraries": { - "endpoint": "api/libraries", - "name": "Audiobookshelf Libraries", + "endpoint": "api/libraries", + "name": "Audiobookshelf Libraries", "data_function": count_libraries, - "attributes_function": get_library_stats + "attributes_function": get_library_stats, + "unit": "libraries", }, } -async def async_setup_platform(hass: HomeAssistant, config, async_add_entities, discovery_info=None): + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: """Set up the sensor platform.""" + conf = entry.data + + _LOGGER.debug("Configuration data: %s", clean_config(conf.copy())) - conf = hass.data.get(DOMAIN) - if conf is None: - _LOGGER.error("Configuration not found in hass.data") - return + global API_URL, API_KEY, SCAN_INTERVAL # noqa: PLW0603 + API_URL = conf[CONF_URL] + API_KEY = conf[CONF_API_KEY] + SCAN_INTERVAL = timedelta(seconds=conf[CONF_SCAN_INTERVAL]) - global API_URL - API_URL = conf["api_url"] - global API_KEY - API_KEY = conf["api_key"] - global SCAN_INTERVAL - SCAN_INTERVAL = timedelta(seconds=conf["scan_interval"]) + async with aiohttp.ClientSession() as session: + headers = {"Authorization": f"Bearer {API_KEY}"} + async with session.get(f"{API_URL}/api/users", headers=headers) as response: + if response.status != HTTP_OK: + msg = f"Failed to connect to API: {response.status}" + _LOGGER.error("%s", msg) + raise ConfigEntryNotReady(msg) coordinator = AudiobookshelfDataUpdateCoordinator(hass) await coordinator.async_config_entry_first_refresh() + libraries = await coordinator.get_libraries() + coordinator.generate_library_sensors(libraries) + entities = [ - AudiobookshelfSensor(coordinator, sensors["users"]), - AudiobookshelfSensor(coordinator, sensors["sessions"]), - AudiobookshelfSensor(coordinator, sensors["libraries"]) + AudiobookshelfSensor(coordinator, sensor) for sensor in sensors.values() ] - async_add_entities(entities, True) + + _LOGGER.debug("Sensors: %s", sensors) + + async_add_entities(entities, update_before_add=True) + + +@dataclass +class LibraryFolder: + """Class representing a folder in an Audiobookshelf library.""" + + id: str + full_path: str + library_id: str + added_at: int | None + + +@dataclass +class LibrarySettings: + """Class representing settings for an Audiobookshelf library.""" + + cover_aspect_ratio: int | None + disable_watcher: bool | None + auto_scan_cron_expression: str | None + skip_matching_media_with_asin: bool | None + skip_matching_media_with_isbn: bool | None + audiobooks_only: bool + epubs_allow_scripted_content: bool | None + hide_single_book_series: bool | None + only_show_later_books_in_continue_series: bool | None + metadata_precedence: list[str] | None + mark_as_finished_percent_complete: int | None + mark_as_finished_time_remaining: int | None + + +@dataclass +class Library: + """Class representing an Audiobookshelf library.""" + + id: str + name: str + folders: list[LibraryFolder] | None + display_order: int | None + icon: str | None + media_type: str + provider: str | None + settings: LibrarySettings + last_scan: int | None + last_scan_version: str | None + created_at: int | None + last_update: int | None + + +T = TypeVar("T", dict, list) + + +def camel_to_snake(data: T) -> T: + """Convert camelCase keys to snake_case.""" + + def _convert_key(key: str) -> str: + return "".join( + ["_" + char.lower() if char.isupper() else char for char in key] + ).lstrip("_") + + if isinstance(data, dict): + return { + _convert_key(key): camel_to_snake(value) + if isinstance(value, dict) + else camel_to_snake(list(value)) + if isinstance(value, list) + else value + for key, value in data.items() + } + if isinstance(data, list): + return [ + camel_to_snake(dict(item)) + if isinstance(item, dict) + else camel_to_snake(list(item)) + if isinstance(item, list) + else item + for item in data + ] + return data + class AudiobookshelfDataUpdateCoordinator(DataUpdateCoordinator): """Class to manage fetching Audiobookshelf data from the API.""" - def __init__(self, hass: HomeAssistant): + def __init__(self, hass: HomeAssistant) -> None: """Initialize.""" super().__init__( hass, @@ -182,28 +248,104 @@ def __init__(self, hass: HomeAssistant): name="audiobookshelf", update_interval=SCAN_INTERVAL, ) - - async def _async_update_data(self): + self.libraries = {} + + async def get_libraries(self) -> list[Library]: + """Fetch library id list from API.""" + async with aiohttp.ClientSession() as session: + headers = {"Authorization": f"Bearer {API_KEY}"} + async with session.get( + f"{API_URL}/api/libraries", headers=headers + ) as response: + if response.status == HTTP_OK: + data: dict[str, Any] = await response.json() + return [ + from_dict( + data_class=Library, + data=( + _LOGGER.debug("Converting library: %s", lib) + or camel_to_snake(lib) + ), + config=Config( + check_types=False, + cast=[str, int], + ), + ) + for lib in data["libraries"] + ] + return [] + + def generate_library_sensors(self, libraries: list[Library]) -> None: + """Generate sensor configs for each library.""" + for library in libraries: + base_id = f"library_{library.id}" + sensors.update( + { + f"{base_id}_size": { + "endpoint": f"api/libraries/{library.id}/stats", + "name": f"{library.name} Size", + "unique_id": f"{base_id}_size", + "data_function": ( + lambda data: get_total_size(data.get("totalSize")) + ), + "unit": "GB", + "attributes_function": do_nothing, + }, + f"{base_id}_items": { + "endpoint": f"api/libraries/{library.id}/stats", + "name": f"{library.name} Items", + "unique_id": f"{base_id}_items", + "data_function": lambda data: data.get("totalItems"), + "unit": "items", + "attributes_function": do_nothing, + }, + f"{base_id}_duration": { + "endpoint": f"api/libraries/{library.id}/stats", + "name": f"{library.name} Duration", + "unique_id": f"{base_id}_duration", + "data_function": ( + lambda data: get_total_duration(data.get("totalDuration")) + ), + "unit": "hours", + "attributes_function": do_nothing, + }, + } + ) + + async def _async_update_data(self) -> dict: """Fetch data from API endpoint.""" headers = {"Authorization": f"Bearer {API_KEY}"} data = {} try: async with aiohttp.ClientSession() as session: for sensor in sensors: - async with session.get(f"{API_URL}/{sensors[sensor]["endpoint"]}", headers=headers) as response: - if response.status != 200: - raise UpdateFailed(f"Error fetching data: {response.status}") + async with session.get( + f"{API_URL}/{sensors[sensor]["endpoint"]}", headers=headers + ) as response: + if response.status != HTTP_OK: + error_message = f"Error fetching data: {response.status}" + raise UpdateFailed(error_message) data[sensors[sensor]["endpoint"]] = await response.json() - return data + _LOGGER.debug( + "Data returns for %s", + f"{API_URL}/{sensors[sensor]["endpoint"]}", + ) + return data # noqa: TRY300 except aiohttp.ClientError as err: - raise UpdateFailed(f"Error fetching data: {err}") + msg = "Error fetching data" + raise UpdateFailed(msg) from err + class AudiobookshelfSensor(Entity): """Representation of a sensor.""" - def __init__(self, coordinator: AudiobookshelfDataUpdateCoordinator, sensor: Sensor): + def __init__( + self, coordinator: AudiobookshelfDataUpdateCoordinator, sensor: Sensor + ) -> None: """Initialize the sensor.""" self._name = sensor["name"] + self._unique_id = sensor.get("unique_id", self._name) + self._unit = sensor.get("unit", None) self._endpoint = sensor["endpoint"] self.coordinator = coordinator self._state = None @@ -212,46 +354,55 @@ def __init__(self, coordinator: AudiobookshelfDataUpdateCoordinator, sensor: Sen self._process_attributes = sensor["attributes_function"] @property - def name(self): + def name(self) -> str: """Return the name of the sensor.""" return self._name @property - def state(self): + def state(self) -> Any: """Return the state of the sensor.""" return self._state @property - def extra_state_attributes(self): + def extra_state_attributes(self) -> dict: """Return the state attributes.""" return self._attributes @property - def device_info(self): + def device_info(self) -> dict: """Return device information about this entity.""" return { - "identifiers": {(DOMAIN, "audiobookshelf_id")}, + "identifiers": {(DOMAIN, API_URL)}, "name": "Audiobookshelf", - "manufacturer": "My Company", - "model": "My Model", - "sw_version": "1.0", + "manufacturer": "advplyr", + "configuration_url": API_URL, } - async def async_update(self): + @property + def unique_id(self) -> str: + """Return a unique ID.""" + # Create unique IDs for each sensor that include the API URL + return f"{API_URL}_{self._endpoint}_{self._name}" + + async def async_update(self) -> None: """Fetch new state data for the sensor.""" data = self.coordinator.data if data: endpoint_data = data.get(self._endpoint, {}) if isinstance(endpoint_data, dict): - self._attributes.update(await self._process_attributes(endpoint_data)) - self._state = await self._process_data(data = endpoint_data) + self._attributes.update(self._process_attributes(endpoint_data)) + new_state = self._process_data(data=endpoint_data) + if new_state not in (0, None) or self._state in (0, None): + self._state = new_state else: - _LOGGER.error("Expected endpoint_data to be a dictionary, got %s", type(endpoint_data)) - _LOGGER.debug(f"Data: {endpoint_data}") + _LOGGER.error( + "Expected endpoint_data to be a dictionary, got %s", + type(endpoint_data), + ) + _LOGGER.debug("Data: %s", endpoint_data) - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """When entity is added to hass.""" self.async_on_remove( self.coordinator.async_add_listener(self.async_write_ha_state) ) - diff --git a/hacs.json b/hacs.json index af39f11..fe43119 100644 --- a/hacs.json +++ b/hacs.json @@ -1,6 +1,6 @@ { "name": "Audiobookshelf", "hacs": "1.6.0", - "homeassistant": "2024.11.0", + "homeassistant": "2024.11.2", "render_readme": true } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index f5baf5a..194e008 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,6 @@ colorlog==6.9.0 -homeassistant==2024.11.0 +homeassistant==2024.11.2 pip>=21.3.1 -ruff==0.7.2 \ No newline at end of file +ruff==0.7.2 +libpcap>=1.11.0b14 +dacite==1.8.1 diff --git a/scripts/setup b/scripts/setup index 141d19f..be7d602 100755 --- a/scripts/setup +++ b/scripts/setup @@ -4,4 +4,10 @@ set -e cd "$(dirname "$0")/.." +sudo apt update -y + +sudo apt-get install -y python3 python3-dev python3-venv python3-pip bluez libffi-dev libssl-dev libjpeg-dev zlib1g-dev autoconf build-essential libopenjp2-7 libtiff6 libturbojpeg0-dev tzdata ffmpeg liblapack3 liblapack-dev libatlas-base-dev + python3 -m pip install --requirement requirements.txt + +python3 -m pip install wheel From c7df290a581401f6cf2274ee48a5c78588121f0a Mon Sep 17 00:00:00 2001 From: jadon <16850875+wolffshots@users.noreply.github.com> Date: Thu, 21 Nov 2024 12:02:05 +0000 Subject: [PATCH 16/20] feat!: refactor code again This redoes a lot of the config flow and sensor details and adds proper sensors for the various aspects of the library that one might want to track. It also better handles errors and implements automated releases. I ended up switching to a `requirements.txt` for pip just because it's easier to manage than poetry There is the start of support for translations and testing as well. --- .github/workflows/hacs.yml | 14 +- .github/workflows/lint.yml | 68 ++----- .github/workflows/release.yml | 29 +++ .github/workflows/test.yml | 55 ----- .github/workflows/tests.yml | 29 +++ .ruff.toml | 7 +- .vscode/launch.json | 8 +- README.md | 45 ++++- custom_components/audiobookshelf/__init__.py | 29 ++- .../audiobookshelf/config_flow.py | 70 ++++++- custom_components/audiobookshelf/const.py | 4 + .../audiobookshelf/manifest.json | 2 +- custom_components/audiobookshelf/sensor.py | 89 +++++---- .../audiobookshelf/translations/en.json | 46 +++++ docs/hass-audiobookshelf-config.png | Bin 242241 -> 77658 bytes docs/hass-audiobookshelf-example.png | Bin 0 -> 316544 bytes hacs.json | 3 +- info.md | 49 ++++- requirements.txt | 1 + tests/__init__.py | 1 + tests/test_sensor.py | 188 ++++++++++++++++++ 21 files changed, 559 insertions(+), 178 deletions(-) create mode 100644 .github/workflows/release.yml delete mode 100644 .github/workflows/test.yml create mode 100644 .github/workflows/tests.yml create mode 100644 custom_components/audiobookshelf/translations/en.json create mode 100644 docs/hass-audiobookshelf-example.png create mode 100644 tests/__init__.py create mode 100644 tests/test_sensor.py diff --git a/.github/workflows/hacs.yml b/.github/workflows/hacs.yml index 9e3cd9a..668ffbb 100644 --- a/.github/workflows/hacs.yml +++ b/.github/workflows/hacs.yml @@ -2,28 +2,32 @@ name: Validate on: push: + branches: + - "main" pull_request: + branches: + - "main" schedule: - cron: "00 12 * * *" workflow_dispatch: jobs: - validate-hassfest: + hassfest: # https://developers.home-assistant.io/blog/2020/04/16/hassfest name: Hassfest validation runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v3 + uses: "actions/checkout@v4.2.2" - name: validation uses: home-assistant/actions/hassfest@master - validate-hacs: + validate-hacs: # https://github.com/hacs/action name: HACS validation runs-on: "ubuntu-latest" steps: - name: checkout - uses: actions/checkout@v3 + uses: "actions/checkout@v4.2.2" - name: validation uses: "hacs/action@main" with: - category: "integration" \ No newline at end of file + category: "integration" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5633a29..b17a721 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,52 +8,28 @@ on: paths: - '**.py' -env: - POETRY_VERSION: 1.5.1 - jobs: - mypy: - runs-on: ubuntu-latest - steps: - - name: Checkout Audiobookshelf - uses: actions/checkout@v3 - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - name: Load cached Poetry installation - id: cached-poetry - uses: actions/cache@v4 - with: - path: ~/.local - key: poetry-${{ env.POETRY_VERSION }} - - name: Install Poetry - uses: snok/install-poetry@v1 - with: - version: ${{ env.POETRY_VERSION }} - virtualenvs-create: true - virtualenvs-in-project: true - installer-parallel: true - - name: cache dependencies - id: cache-deps - uses: actions/cache@v4 - with: - path: .venv - key: pydeps-${{ hashFiles('**/poetry.lock') }} - - name: Install dependencies - run: poetry install --no-interaction --no-root - if: steps.cache-deps.outputs.cache-hit != 'true' - - name: Run MyPy - run: | - source .venv/bin/activate \ - mypy ./custom_components/audiobookshelf ruff: - runs-on: ubuntu-latest + name: "Ruff" + runs-on: "ubuntu-latest" steps: - - name: Checkout Audiobookshelf - uses: actions/checkout@v4 - - name: Run Ruff - uses: chartboost/ruff-action@v1 - with: - version: 0.0.272 - src: './custom_components/audiobookshelf' \ No newline at end of file + - name: "Checkout the repository" + uses: "actions/checkout@v4.2.2" + + - name: "Set up Python" + uses: actions/setup-python@v5.3.0 + with: + python-version: "3.12" + cache: "pip" + + - name: "Install requirements" + run: python3 -m pip install -r requirements.txt + + - name: "Lint" + run: python3 -m ruff check ./custom_components/audiobookshelf + + - name: Run MyPy + run: python3 -m mypy custom_components/audiobookshelf + + - name: "Format" + run: python3 -m ruff format ./custom_components/audiobookshelf --check diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d2380cc --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,29 @@ +name: "Release" + +on: + release: + types: + - "published" + +permissions: {} + +jobs: + release: + name: "Release" + runs-on: "ubuntu-latest" + permissions: + contents: write + steps: + - name: "Checkout the repository" + uses: "actions/checkout@v4.2.2" + + - name: "ZIP the integration directory" + shell: "bash" + run: | + cd "${{ github.workspace }}/custom_components/audiobookshelf" + zip "Audiobookshelf_${{ github.ref_name }}.zip" -r ./ + + - name: "Upload the ZIP file to the release" + uses: "softprops/action-gh-release@v2.0.9" + with: + files: ${{ github.workspace }}/custom_components/audiobookshelf/Audiobookshelf_${{ github.ref_name }}.zip diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 42ac510..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Test - -on: - push: - pull_request: - -env: - POETRY_VERSION: 1.5.1 - -jobs: - tests: - runs-on: "ubuntu-latest" - name: Run tests - steps: - - name: Check out code - uses: actions/checkout@v4 - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - name: Load cached Poetry installation - id: cached-poetry - uses: actions/cache@v4 - with: - path: ~/.local - key: poetry-${{ env.POETRY_VERSION }} - - name: Install Poetry - uses: snok/install-poetry@v1 - with: - version: ${{ env.POETRY_VERSION }} - virtualenvs-create: true - virtualenvs-in-project: true - installer-parallel: true - - name: cache dependencies - id: cache-deps - uses: actions/cache@v4 - with: - path: .venv - key: pydeps-${{ hashFiles('**/poetry.lock') }} - - name: Install dependencies - run: poetry install --no-interaction --no-root - if: steps.cache-deps.outputs.cache-hit != 'true' - - name: Run tests - run: | - source .venv/bin/activate - pytest \ - -qq \ - --timeout=10 \ - --durations=10 \ - -n auto \ - --cov custom_components.audiobookshelf \ - --cov-report xml \ - -o console_output_style=count \ - -p no:sugar \ - tests \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..93092ca --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,29 @@ +name: Test + +on: + push: + paths: + - '**.py' + pull_request: + paths: + - '**.py' + +jobs: + ruff: + name: "Ruff" + runs-on: "ubuntu-latest" + steps: + - name: "Checkout the repository" + uses: "actions/checkout@v4.2.2" + + - name: "Set up Python" + uses: actions/setup-python@v5.3.0 + with: + python-version: "3.12" + cache: "pip" + + - name: "Install requirements" + run: python3 -m pip install -r requirements.txt + + - name: Run tests + run: python3 -m pytest tests -W ignore::DeprecationWarning diff --git a/.ruff.toml b/.ruff.toml index 3dfe628..54227fe 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -14,7 +14,7 @@ ignore = [ "D212", # multi-line-summary-first-line (incompatible with formatter) "COM812", # incompatible with formatter "ISC001", # incompatible with formatter - "ERA001", commented code isn't always bad + "ERA001", # commented code isn't always bad ] [lint.flake8-pytest-style] @@ -24,4 +24,7 @@ fixture-parentheses = false keep-runtime-typing = true [lint.mccabe] -max-complexity = 25 \ No newline at end of file +max-complexity = 25 + +[lint.per-file-ignores] +"tests/*.py" = ["S101"] diff --git a/.vscode/launch.json b/.vscode/launch.json index d189c65..ec1d241 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,10 +6,14 @@ "configurations": [ { "name": "Python: Module", - "type": "python", + "type": "debugpy", "request": "launch", "module": "pytest", - "args": ["tests", "--cov=custom_components", "--cov-report=term-missing"], + "args": [ + "tests", + "--cov=custom_components", + "--cov-report=term-missing" + ], "justMyCode": false } ] diff --git a/README.md b/README.md index d284347..8c1c8b9 100644 --- a/README.md +++ b/README.md @@ -11,25 +11,44 @@ [![Project Maintenance][maintenance-shield]][user_profile] [![BuyMeCoffee][buymecoffeebadge]][buymecoffee] -**This component will set up the following sensors.** +## This component will set up the following general sensors: | Entity | Type | Description | | --------------- | ---------------- | ------------------------------------ | -| `connectivity` | `binary_sensor` | Show whether the server is connected | -| `sessions` | `sensor` | Show number of open audio sessions | +| `open_sessions` | `sensor` | Show number of open audio sessions | | `libraries` | `sensor` | Number of libraries on the server | +| `users` | `sensor` | Number of users on the server | + +## It also adds the following library specific sensors (for each library that it finds during setup): +| Entity | Type | Description | +| ------------------ | -------------- | -------------------------------------------------------- | +| `items` | `sensor` | Number of items in the library | +| `duration` | `sensor` | Total time in hours of playable content in library | +| `size` | `sensor` | Total disk space used by library in GB | + +## Examples + +![Example of sensors on device](docs/hass-audiobookshelf-example.png) ## Installation +### Installation with HACS + +1. Make sure you have HACS fully set up (if you don't you can do so [here](https://hacs.xyz/docs/use/)) +2. Open up HACS in you Home Assistant instance and search for "Audiobookshelf" and add it +3. Restart Home Assistant once it is installed +4. In the Home Assistant UI go to "Configuration" -> "Integrations" click "+" and search for "Audiobookshelf" +5. Click on "Audiobookshelf" and proceed to [Configuration](#configuration) + ### Manual installation -1. Using the tool of choice open the directory (folder) for your HA configuration (where you find `configuration.yaml`). -2. If you do not have a `custom_components` directory (folder) there, you need to create it. -3. In the `custom_components` directory (folder) create a new folder called `audiobookshelf`. -4. Download _all_ the files from the `custom_components/audiobookshelf/` directory (folder) in this repository. -5. Place the files you downloaded in the new directory (folder) you created. -6. Restart Home Assistant -7. In the HA UI go to "Configuration" -> "Integrations" click "+" and search for "Audiobookshelf" +1. Using the tool of choice open the directory (folder) for your Home Assistant configuration (where you find `configuration.yaml`) +2. If you do not have a `custom_components` directory (folder) there, you need to create it +3. Download the `Audiobookshelf_vX.X.X.zip` file from the [latest release](https://github.com/wolffshots/hass-audiobookshelf/releases/latest) +4. Unzip the folder and place it into `custom_components` +5. Restart Home Assistant +6. In the Home Assistant UI go to "Configuration" -> "Integrations" click "+" and search for "Audiobookshelf" +7. Click on "Audiobookshelf" and proceed to [Configuration](#configuration) ## Configuration @@ -45,6 +64,12 @@ For more info on what the token can be used for see: https://api.audiobookshelf. ### Setting up via the UI ![Config in UI](docs/hass-audiobookshelf-config.png) +| Variable | Description | +| --------------- | --------------------------------------------------------------------------------------------------------- | +| `URL` | The URL and port of your Audiobookshelf instance (must start with the protocol, http:// or https://) | +| `API key` | The API key that you got in the previous step | +| `Scan interval` | How regularly the data should be fetched from your Audiobookshelf instance (in seconds), defaults to 300s | + ## Credits This project was generated from [@oncleben31](https://github.com/oncleben31)'s [Home Assistant Custom Component Cookiecutter](https://github.com/oncleben31/cookiecutter-homeassistant-custom-component) template. diff --git a/custom_components/audiobookshelf/__init__.py b/custom_components/audiobookshelf/__init__.py index 59d93f2..cf122a3 100644 --- a/custom_components/audiobookshelf/__init__.py +++ b/custom_components/audiobookshelf/__init__.py @@ -9,7 +9,9 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import config_validation as cv -from .const import DOMAIN, PLATFORMS +from custom_components.audiobookshelf.config_flow import validate_config + +from .const import DOMAIN, ISSUE_URL, PLATFORMS, VERSION CONFIG_SCHEMA = vol.Schema( { @@ -27,10 +29,13 @@ _LOGGER = logging.getLogger(__name__) -def clean_config(data: dict) -> dict: +def clean_config(data: dict[str, str]) -> dict[str, str]: """Remove the api key from config.""" - if bool(data[CONF_API_KEY]): - data[CONF_API_KEY] = "" + try: + if bool(data[CONF_API_KEY]): + data[CONF_API_KEY] = "" + except Exception: + _LOGGER.exception("Failed to clean config, most likely not valid") return data @@ -40,10 +45,26 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: error_message = "Configuration not found" raise ConfigEntryNotReady(error_message) + _LOGGER.info( + """ + ------------------------------------------------------------------- + Audiobookshelf + Version: %s + This is a custom integration! + If you have any issues with this you need to open an issue here: + %s + ------------------------------------------------------------------- + """, + VERSION, + ISSUE_URL, + ) + _LOGGER.debug( "Setting up Audiobookshelf with config: %s", clean_config(entry.data.copy()) ) + validate_config(entry.data.copy()) + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True diff --git a/custom_components/audiobookshelf/config_flow.py b/custom_components/audiobookshelf/config_flow.py index 7f44367..0b0f6f7 100644 --- a/custom_components/audiobookshelf/config_flow.py +++ b/custom_components/audiobookshelf/config_flow.py @@ -2,12 +2,54 @@ from __future__ import annotations +import logging + +import aiohttp import homeassistant.helpers.config_validation as cv import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_API_KEY, CONF_SCAN_INTERVAL, CONF_URL -from .const import DOMAIN +from .const import DOMAIN, HTTP_AUTH_FAILURE, HTTP_OK + +_LOGGER = logging.getLogger(__name__) + + +def validate_config(data: dict[str, str]) -> dict: + """Validate the config entries.""" + errors = {} + if not bool(data[CONF_API_KEY]): + errors[CONF_API_KEY] = "api_key_invalid" + if not bool(data[CONF_URL]): + errors[CONF_URL] = "url_invalid" + if not data[CONF_URL].startswith(("http://", "https://")): + errors[CONF_URL] = "url_protocol_missing" + if not bool(data[CONF_SCAN_INTERVAL]): + errors[CONF_SCAN_INTERVAL] = "scan_interval_invalid" + return errors + + +async def verify_config(data: dict) -> dict: + """Verify the configuration by testing the API connection.""" + async with aiohttp.ClientSession() as session: + headers = {"Authorization": f"Bearer {data[CONF_API_KEY]}"} + try: + async with session.get( + f"{data[CONF_URL]}/api/libraries", + headers=headers, + timeout=aiohttp.ClientTimeout(5), + ) as response: + if response.status != HTTP_OK: + msg = f"Failed to connect to API: {response.status}" + _LOGGER.error("%s", msg) + if response.status == HTTP_AUTH_FAILURE: + return {"base": "api_auth_error"} + return {"base": "api_other_error"} + return {} + except TimeoutError: + return {"base": "api_timeout_error"} + except aiohttp.ClientConnectorError: + return {"base": "api_client_connector_error"} class AudiobookshelfConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): @@ -16,13 +58,35 @@ class AudiobookshelfConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 async def async_step_user( - self, user_input: dict | None = None + self, user_input: dict[str, str] | None = None ) -> config_entries.ConfigFlowResult: """Handle the user step.""" errors = {} if user_input is not None: - # Process user input + errors.update(validate_config(user_input)) + if not errors: + errors.update(await verify_config(user_input)) + if errors: + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required( + CONF_URL, default=user_input.get(CONF_URL, "") + ): str, + vol.Required( + CONF_API_KEY, default=user_input.get(CONF_API_KEY, "") + ): str, + vol.Optional( + CONF_SCAN_INTERVAL, + default=user_input.get(CONF_SCAN_INTERVAL, 300), + ): cv.positive_int, + } + ), + errors=errors, + ) + return self.async_create_entry( title="Audiobookshelf", data={ diff --git a/custom_components/audiobookshelf/const.py b/custom_components/audiobookshelf/const.py index da2f91d..63be934 100644 --- a/custom_components/audiobookshelf/const.py +++ b/custom_components/audiobookshelf/const.py @@ -2,5 +2,9 @@ from homeassistant.const import Platform +VERSION = "v0.2.0" +ISSUE_URL = "https://github.com/wolffshots/hass-audiobookshelf/issues" DOMAIN = "audiobookshelf" PLATFORMS: list[Platform] = [Platform.SENSOR] +HTTP_OK = 200 +HTTP_AUTH_FAILURE = 401 diff --git a/custom_components/audiobookshelf/manifest.json b/custom_components/audiobookshelf/manifest.json index 4d945cc..a742783 100644 --- a/custom_components/audiobookshelf/manifest.json +++ b/custom_components/audiobookshelf/manifest.json @@ -1,7 +1,7 @@ { "domain": "audiobookshelf", "name": "Audiobookshelf", - "version": "0.1.1", + "version": "0.2.0", "documentation": "https://github.com/wolffshots/hass-audiobookshelf", "iot_class": "local_polling", "issue_tracker": "https://github.com/wolffshots/hass-audiobookshelf/issues", diff --git a/custom_components/audiobookshelf/sensor.py b/custom_components/audiobookshelf/sensor.py index 282fae5..a4d0dc3 100644 --- a/custom_components/audiobookshelf/sensor.py +++ b/custom_components/audiobookshelf/sensor.py @@ -3,28 +3,28 @@ import logging from dataclasses import dataclass from datetime import timedelta -from typing import Any, TypeVar +from typing import Any import aiohttp -from dacite import Config, from_dict +from dacite import from_dict from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_API_KEY, CONF_SCAN_INTERVAL, CONF_URL from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.update_coordinator import ( DataUpdateCoordinator, UpdateFailed, ) from custom_components.audiobookshelf import clean_config +from custom_components.audiobookshelf.const import DOMAIN, HTTP_OK, VERSION _LOGGER = logging.getLogger(__name__) -DOMAIN = "audiobookshelf" -HTTP_OK = 200 - def count_active_users(data: dict) -> int: """Take in an object with an array of users and counts the active ones.""" @@ -138,18 +138,19 @@ async def async_setup_entry( async with aiohttp.ClientSession() as session: headers = {"Authorization": f"Bearer {API_KEY}"} - async with session.get(f"{API_URL}/api/users", headers=headers) as response: + async with session.get(f"{API_URL}/api/libraries", headers=headers) as response: if response.status != HTTP_OK: msg = f"Failed to connect to API: {response.status}" _LOGGER.error("%s", msg) raise ConfigEntryNotReady(msg) coordinator = AudiobookshelfDataUpdateCoordinator(hass) - await coordinator.async_config_entry_first_refresh() - libraries = await coordinator.get_libraries() + libraries: list[Library] = await coordinator.get_libraries() coordinator.generate_library_sensors(libraries) + await coordinator.async_config_entry_first_refresh() + entities = [ AudiobookshelfSensor(coordinator, sensor) for sensor in sensors.values() ] @@ -205,10 +206,7 @@ class Library: last_update: int | None -T = TypeVar("T", dict, list) - - -def camel_to_snake(data: T) -> T: +def camel_to_snake(data: dict[str, Any] | list[Any]) -> dict[str, Any] | list[Any]: """Convert camelCase keys to snake_case.""" def _convert_key(key: str) -> str: @@ -248,7 +246,6 @@ def __init__(self, hass: HomeAssistant) -> None: name="audiobookshelf", update_interval=SCAN_INTERVAL, ) - self.libraries = {} async def get_libraries(self) -> list[Library]: """Fetch library id list from API.""" @@ -259,17 +256,12 @@ async def get_libraries(self) -> list[Library]: ) as response: if response.status == HTTP_OK: data: dict[str, Any] = await response.json() + for lib in data["libraries"]: + _LOGGER.debug("Converting library: %s", lib) return [ from_dict( data_class=Library, - data=( - _LOGGER.debug("Converting library: %s", lib) - or camel_to_snake(lib) - ), - config=Config( - check_types=False, - cast=[str, int], - ), + data=(dict(camel_to_snake(lib))), ) for lib in data["libraries"] ] @@ -283,7 +275,7 @@ def generate_library_sensors(self, libraries: list[Library]) -> None: { f"{base_id}_size": { "endpoint": f"api/libraries/{library.id}/stats", - "name": f"{library.name} Size", + "name": f"Audiobookshelf {library.name} Size", "unique_id": f"{base_id}_size", "data_function": ( lambda data: get_total_size(data.get("totalSize")) @@ -293,7 +285,7 @@ def generate_library_sensors(self, libraries: list[Library]) -> None: }, f"{base_id}_items": { "endpoint": f"api/libraries/{library.id}/stats", - "name": f"{library.name} Items", + "name": f"Audiobookshelf {library.name} Items", "unique_id": f"{base_id}_items", "data_function": lambda data: data.get("totalItems"), "unit": "items", @@ -301,7 +293,7 @@ def generate_library_sensors(self, libraries: list[Library]) -> None: }, f"{base_id}_duration": { "endpoint": f"api/libraries/{library.id}/stats", - "name": f"{library.name} Duration", + "name": f"Audiobookshelf {library.name} Duration", "unique_id": f"{base_id}_duration", "data_function": ( lambda data: get_total_duration(data.get("totalDuration")) @@ -318,17 +310,24 @@ async def _async_update_data(self) -> dict: data = {} try: async with aiohttp.ClientSession() as session: - for sensor in sensors: + unique_endpoints: set[str] = { + sensor["endpoint"] for sensor in sensors.values() + } + _LOGGER.debug( + "Unique endpoints:\n%s", + "\n".join(endpoint for endpoint in unique_endpoints), + ) + for endppoint in unique_endpoints: async with session.get( - f"{API_URL}/{sensors[sensor]["endpoint"]}", headers=headers + f"{API_URL}/{endppoint}", headers=headers ) as response: if response.status != HTTP_OK: error_message = f"Error fetching data: {response.status}" raise UpdateFailed(error_message) - data[sensors[sensor]["endpoint"]] = await response.json() + data[endppoint] = await response.json() _LOGGER.debug( "Data returns for %s", - f"{API_URL}/{sensors[sensor]["endpoint"]}", + f"{API_URL}/{endppoint}", ) return data # noqa: TRY300 except aiohttp.ClientError as err: @@ -336,7 +335,7 @@ async def _async_update_data(self) -> dict: raise UpdateFailed(msg) from err -class AudiobookshelfSensor(Entity): +class AudiobookshelfSensor(RestoreEntity, Entity): """Representation of a sensor.""" def __init__( @@ -345,14 +344,25 @@ def __init__( """Initialize the sensor.""" self._name = sensor["name"] self._unique_id = sensor.get("unique_id", self._name) - self._unit = sensor.get("unit", None) + self._attr_unit_of_measurement = sensor.get("unit", None) self._endpoint = sensor["endpoint"] self.coordinator = coordinator - self._state = None - self._attributes = {} + self._state: str | None = None + self._attr_extra_state_attributes = {} self._process_data = sensor["data_function"] self._process_attributes = sensor["attributes_function"] + async def async_added_to_hass(self) -> None: + """Call when entity is added to hass.""" + await super().async_added_to_hass() + if (last_state := await self.async_get_last_state()) is not None: + self._state = last_state.state + self._attributes = last_state.attributes + + self.async_on_remove( + self.coordinator.async_add_listener(self.async_write_ha_state) + ) + @property def name(self) -> str: """Return the name of the sensor.""" @@ -366,15 +376,16 @@ def state(self) -> Any: @property def extra_state_attributes(self) -> dict: """Return the state attributes.""" - return self._attributes + return self._attr_extra_state_attributes @property - def device_info(self) -> dict: + def device_info(self) -> DeviceInfo | None: """Return device information about this entity.""" return { "identifiers": {(DOMAIN, API_URL)}, "name": "Audiobookshelf", "manufacturer": "advplyr", + "sw_version": VERSION, "configuration_url": API_URL, } @@ -390,7 +401,9 @@ async def async_update(self) -> None: if data: endpoint_data = data.get(self._endpoint, {}) if isinstance(endpoint_data, dict): - self._attributes.update(self._process_attributes(endpoint_data)) + self._attr_extra_state_attributes.update( + self._process_attributes(endpoint_data) + ) new_state = self._process_data(data=endpoint_data) if new_state not in (0, None) or self._state in (0, None): self._state = new_state @@ -400,9 +413,3 @@ async def async_update(self) -> None: type(endpoint_data), ) _LOGGER.debug("Data: %s", endpoint_data) - - async def async_added_to_hass(self) -> None: - """When entity is added to hass.""" - self.async_on_remove( - self.coordinator.async_add_listener(self.async_write_ha_state) - ) diff --git a/custom_components/audiobookshelf/translations/en.json b/custom_components/audiobookshelf/translations/en.json new file mode 100644 index 0000000..31cef21 --- /dev/null +++ b/custom_components/audiobookshelf/translations/en.json @@ -0,0 +1,46 @@ +{ + "config": { + "step": { + "user": { + "title": "Audiobookshelf", + "data": { + "url": "API URL (starting with http:// or https://)", + "api_key": "API Key", + "scan_interval": "Update interval in seconds (defaults to 300s/5min)" + } + } + }, + "error": { + "api_auth_error": "Invalid authentication", + "api_other_error": "Other error querying API", + "api_key_invalid": "API key is missing or invalid", + "url_invalid": "URL is missing or invalid", + "scan_interval_invalid": "Scan interval is missing or invalid", + "url_protocol_missing": "URL must start with http:// or https://", + "api_timeout_error": "Timed out waiting for API", + "api_client_connector_error": "Client couldn't connect to API", + "unknown": "Unexpected error" + }, + "abort": { + "already_configured": "Device is already configured" + } + }, + "entity": { + "sensor": { + "audiobookshelf_users": { + "name": "Users", + "state_attributes": { + "active_users": "Active Users", + "total_users": "Total Users" + } + }, + "audiobookshelf_libraries": { + "name": "Libraries", + "state_attributes": { + "total_items": "Total Items", + "total_duration": "Total Duration" + } + } + } + } +} \ No newline at end of file diff --git a/docs/hass-audiobookshelf-config.png b/docs/hass-audiobookshelf-config.png index 653f9e41f49883cadaaa018b61322b7083f7a148..3f2cc19045fcf3b4cf205ceab29b2e9994265498 100644 GIT binary patch literal 77658 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

l9bAT|zUzW7@E+dNcE-RU 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 I+Tg?g0$AjxQUCw| literal 242241 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