From 1dc88d1110a070548e3e8234ee417f191bbcc4db Mon Sep 17 00:00:00 2001 From: cayossarian Date: Tue, 17 Feb 2026 12:25:08 -0800 Subject: [PATCH 01/13] =?UTF-8?q?Phase=201:=20Gen3=20gRPC=20integration=20?= =?UTF-8?q?=E2=80=94=20capability-gated=20platform=20and=20entity=20loadin?= =?UTF-8?q?g?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - const.py: Add CONF_PANEL_GENERATION constant - span_panel_api.py: Add capabilities property delegating to client (falls back to GEN2_FULL) - __init__.py: Replace static PLATFORMS with capability-gated platform loading; store active_platforms per entry so unload is exact - config_flow.py: Add panel generation dropdown (auto/gen2/gen3); add async_step_gen3_setup for gRPC probe without JWT auth - sensors/factory.py: Capability-gate DSM, energy, hardware status, battery, and solar sensor groups; power sensors always created - manifest.json: Bump version to 1.3.2; require span-panel-api[grpc]~=1.1.15 - pyproject.toml: Update span-panel-api dev dep to 1.1.15 with grpc extra - .github/workflows/ci.yml: Update sed command for new dep format and version - scripts/sync-dependencies.py: Handle package extras in version regex - docs/dev/gen3-grpc-integration-plan.md: Add Phase 1 complete / Phase 2 deferred plan --- .github/workflows/ci.yml | 2 +- custom_components/span_panel/__init__.py | 41 ++- custom_components/span_panel/config_flow.py | 96 +++++- custom_components/span_panel/const.py | 1 + custom_components/span_panel/manifest.json | 4 +- .../span_panel/sensors/factory.py | 70 +++-- .../span_panel/span_panel_api.py | 14 +- docs/dev/gen3-grpc-integration-plan.md | 286 ++++++++++++++++++ poetry.lock | 10 +- pyproject.toml | 2 +- scripts/sync-dependencies.py | 4 +- 11 files changed, 490 insertions(+), 40 deletions(-) create mode 100644 docs/dev/gen3-grpc-integration-plan.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc4766d..ad08d66 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: - name: Install dependencies run: | # Replace path dependencies with PyPI versions for CI - sed -i 's/span-panel-api = {path = "..\/span-panel-api", develop = true}/span-panel-api = "^1.1.14"/' pyproject.toml + sed -i 's/span-panel-api = {path = "..\/span-panel-api", develop = true, extras = \["grpc"\]}/span-panel-api = "^1.1.15"/' pyproject.toml sed -i 's/ha-synthetic-sensors = {path = "..\/ha-synthetic-sensors", develop = true}/ha-synthetic-sensors = "^1.1.13"/' pyproject.toml # Regenerate lock file with the modified dependencies poetry lock diff --git a/custom_components/span_panel/__init__.py b/custom_components/span_panel/__init__.py index 459d166..8c6b926 100644 --- a/custom_components/span_panel/__init__.py +++ b/custom_components/span_panel/__init__.py @@ -17,6 +17,7 @@ from homeassistant.core import CoreState, HomeAssistant from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.util import slugify +from span_panel_api import PanelCapability # Import config flow to ensure it's registered from . import config_flow # noqa: F401 # type: ignore[misc] @@ -49,6 +50,20 @@ from .span_panel_api import SpanPanelAuthError, set_async_delay_func from .util import panel_to_device_info +# Platforms that are always loaded regardless of panel generation. +_BASE_PLATFORMS: list[Platform] = [ + Platform.BINARY_SENSOR, + Platform.SENSOR, +] + +# Platforms that are only loaded when the panel advertises the matching capability. +_CAPABILITY_PLATFORMS: dict[PanelCapability, Platform] = { + PanelCapability.RELAY_CONTROL: Platform.SWITCH, + PanelCapability.PRIORITY_CONTROL: Platform.SELECT, +} + +# Convenience constant for callers (e.g. unload) that need the full set — this is +# the Gen2 superset. Per-entry active platforms are stored in hass.data. PLATFORMS: list[Platform] = [ Platform.BINARY_SENSOR, Platform.SELECT, @@ -56,6 +71,9 @@ Platform.SWITCH, ] +# Key for storing the active platform list in hass.data per config entry. +_ACTIVE_PLATFORMS = "active_platforms" + _LOGGER = logging.getLogger(__name__) # Config entry version for unique ID consistency migration @@ -254,10 +272,23 @@ async def _test_authenticated_connection() -> None: entry.async_on_unload(entry.add_update_listener(update_listener)) + # Build the capability-gated platform list for this entry. + capabilities = span_panel.api.capabilities + active_platforms: list[Platform] = list(_BASE_PLATFORMS) + for cap, platform in _CAPABILITY_PLATFORMS.items(): + if cap in capabilities: + active_platforms.append(platform) + _LOGGER.debug( + "Panel capabilities: %s — loading platforms: %s", + capabilities, + [p.value for p in active_platforms], + ) + hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = { COORDINATOR: coordinator, NAME: name, + _ACTIVE_PLATFORMS: active_platforms, } # Generate default device name based on existing devices @@ -301,7 +332,7 @@ async def _test_authenticated_connection() -> None: # PHASE 1 Ensure device is registered BEFORE sensors are created await ensure_device_registered(hass, entry, span_panel, smart_device_name) - await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + await hass.config_entries.async_forward_entry_setups(entry, active_platforms) # Register services await async_setup_cleanup_energy_spikes_service(hass) @@ -357,8 +388,12 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: else: _LOGGER.warning("No coordinator data found for entry %s", entry.entry_id) - _LOGGER.debug("Unloading platforms: %s", PLATFORMS) - unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + # Retrieve the exact set of platforms that were loaded for this entry so we + # unload the same set (capability-gated entries may not have SELECT/SWITCH). + entry_data = hass.data.get(DOMAIN, {}).get(entry.entry_id, {}) + active_platforms = entry_data.get(_ACTIVE_PLATFORMS, PLATFORMS) + _LOGGER.debug("Unloading platforms: %s", [p.value for p in active_platforms]) + unload_ok = await hass.config_entries.async_unload_platforms(entry, active_platforms) if unload_ok: hass.data[DOMAIN].pop(entry.entry_id, None) diff --git a/custom_components/span_panel/config_flow.py b/custom_components/span_panel/config_flow.py index 4036d09..de28413 100644 --- a/custom_components/span_panel/config_flow.py +++ b/custom_components/span_panel/config_flow.py @@ -54,6 +54,7 @@ CONF_API_RETRIES, CONF_API_RETRY_BACKOFF_MULTIPLIER, CONF_API_RETRY_TIMEOUT, + CONF_PANEL_GENERATION, CONF_SIMULATION_CONFIG, CONF_SIMULATION_OFFLINE_MINUTES, CONF_SIMULATION_START_TIME, @@ -108,6 +109,18 @@ def get_user_data_schema(default_host: str = "") -> vol.Schema: vol.Optional("simulator_mode", default=False): bool, vol.Optional(POWER_DISPLAY_PRECISION, default=0): int, vol.Optional(ENERGY_DISPLAY_PRECISION, default=2): int, + vol.Optional(CONF_PANEL_GENERATION, default="auto"): selector( + { + "select": { + "options": [ + {"value": "auto", "label": "Auto-detect"}, + {"value": "gen2", "label": "Gen2 (OpenAPI/HTTP)"}, + {"value": "gen3", "label": "Gen3 (gRPC)"}, + ], + "mode": "dropdown", + } + } + ), } ) @@ -177,6 +190,8 @@ def __init__(self) -> None: # Initial naming selection chosen during pre-setup self._chosen_use_device_prefix: bool | None = None self._chosen_use_circuit_numbers: bool | None = None + # Panel generation selected by the user (auto/gen2/gen3) + self.panel_generation: str = "auto" async def setup_flow( self, trigger_type: TriggerFlowType, host: str, use_ssl: bool = False @@ -216,6 +231,75 @@ async def setup_flow( self._is_flow_setup = True + async def async_step_gen3_setup( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Set up a Gen3 (gRPC) panel — no authentication required. + + Probes port 50065, retrieves the serial number from a snapshot, then + jumps straight to entity naming (skipping the JWT auth steps). + """ + if user_input is None: + # Show a minimal form asking only for the host + schema = vol.Schema({vol.Required(CONF_HOST, default=""): str}) + return self.async_show_form(step_id="gen3_setup", data_schema=schema) + + host: str = str(user_input.get(CONF_HOST, "")).strip() + if not host: + schema = vol.Schema({vol.Required(CONF_HOST, default=""): str}) + return self.async_show_form( + step_id="gen3_setup", + data_schema=schema, + errors={"base": "host_required"}, + ) + + # Probe the gRPC endpoint to confirm connectivity. + try: + from span_panel_api.grpc import ( # pylint: disable=import-outside-toplevel + SpanGrpcClient, + ) + from span_panel_api.grpc.const import ( # pylint: disable=import-outside-toplevel + DEFAULT_GRPC_PORT, + ) + + client = SpanGrpcClient(host, DEFAULT_GRPC_PORT) + connected = await client.connect() + if not connected: + raise ConnectionError("gRPC connect() returned False") + try: + snapshot = await client.get_snapshot() + serial = snapshot.serial_number or host + finally: + await client.close() + except Exception as exc: + _LOGGER.warning("Gen3 gRPC probe failed for host %s: %s", host, exc) + schema = vol.Schema({vol.Required(CONF_HOST, default=host): str}) + return self.async_show_form( + step_id="gen3_setup", + data_schema=schema, + errors={"base": "cannot_connect"}, + ) + + self.host = host + self.serial_number = serial + # Gen3 panels have no authentication — store empty token so downstream + # code that checks `if access_token:` skips auth-related calls. + self.access_token = "" # nosec B105 + self.trigger_flow_type = TriggerFlowType.CREATE_ENTRY + self._is_flow_setup = True + + self.context = { + **self.context, + "title_placeholders": { + **self.context.get("title_placeholders", {}), + CONF_HOST: host, + }, + } + + await self.ensure_not_already_configured() + # Gen3 panels require no auth — go straight to entity naming. + return await self.async_step_choose_entity_naming_initial() + def ensure_flow_is_set_up(self) -> None: """Ensure the flow is set up.""" if self._is_flow_setup is False: @@ -257,11 +341,13 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None) -> Con # Store precision settings from user input (needed for both simulator and regular mode) self.power_display_precision = user_input.get(POWER_DISPLAY_PRECISION, 0) self.energy_display_precision = user_input.get(ENERGY_DISPLAY_PRECISION, 2) + self.panel_generation = user_input.get(CONF_PANEL_GENERATION, "auto") _LOGGER.debug( - "CONFIG_INPUT_DEBUG: User input precision - power: %s, energy: %s, full input: %s", + "CONFIG_INPUT_DEBUG: User input precision - power: %s, energy: %s, generation: %s, full input: %s", self.power_display_precision, self.energy_display_precision, + self.panel_generation, user_input, ) @@ -280,7 +366,12 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None) -> Con use_ssl: bool = user_input.get(CONF_USE_SSL, False) - # Validate host before setting up flow + # Route Gen3 panels to a dedicated setup path (no HTTP auth required). + if self.panel_generation == "gen3": + self.use_ssl = use_ssl + return await self.async_step_gen3_setup({"host": host}) + + # Gen2 / auto-detect: validate via HTTP then proceed through auth flow. if not await validate_host(self.hass, host, use_ssl=use_ssl): return self.async_show_form( step_id="user", @@ -634,6 +725,7 @@ def create_new_entry( CONF_HOST: host, CONF_ACCESS_TOKEN: access_token, CONF_USE_SSL: self.use_ssl, + CONF_PANEL_GENERATION: self.panel_generation, "device_name": device_name, }, options={ diff --git a/custom_components/span_panel/const.py b/custom_components/span_panel/const.py index 57b5d14..9173161 100644 --- a/custom_components/span_panel/const.py +++ b/custom_components/span_panel/const.py @@ -11,6 +11,7 @@ CONF_SERIAL_NUMBER = "serial_number" CONF_USE_SSL = "use_ssl" CONF_DEVICE_NAME = "device_name" +CONF_PANEL_GENERATION = "panel_generation" # Simulation configuration CONF_SIMULATION_CONFIG = "simulation_config" diff --git a/custom_components/span_panel/manifest.json b/custom_components/span_panel/manifest.json index bff2ab9..0e8d59d 100644 --- a/custom_components/span_panel/manifest.json +++ b/custom_components/span_panel/manifest.json @@ -12,9 +12,9 @@ "iot_class": "local_polling", "issue_tracker": "https://github.com/SpanPanel/span/issues", "requirements": [ - "span-panel-api~=1.1.14" + "span-panel-api[grpc]~=1.1.15" ], - "version": "1.3.1", + "version": "1.3.2", "zeroconf": [ { "type": "_span._tcp.local." diff --git a/custom_components/span_panel/sensors/factory.py b/custom_components/span_panel/sensors/factory.py index a939cd7..3b29f8d 100644 --- a/custom_components/span_panel/sensors/factory.py +++ b/custom_components/span_panel/sensors/factory.py @@ -8,6 +8,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers import entity_registry as er +from span_panel_api import PanelCapability from custom_components.span_panel.const import ( ENABLE_CIRCUIT_NET_ENERGY_SENSORS, @@ -54,29 +55,31 @@ def create_panel_sensors( SpanPanelPanelStatus | SpanPanelStatus | SpanPanelPowerSensor | SpanPanelEnergySensor ] = [] + capabilities = span_panel.api.capabilities + # Add panel data status sensors (DSM State, DSM Grid State, etc.) - for description in PANEL_DATA_STATUS_SENSORS: - entities.append(SpanPanelPanelStatus(coordinator, description, span_panel)) + # These are Gen2-only; Gen3 has no DSM state data. + if PanelCapability.DSM_STATE in capabilities: + for description in PANEL_DATA_STATUS_SENSORS: + entities.append(SpanPanelPanelStatus(coordinator, description, span_panel)) - # Add panel power sensors (replacing synthetic ones) + # Add panel power sensors — available for all panel generations. for description in PANEL_POWER_SENSORS: entities.append(SpanPanelPowerSensor(coordinator, description, span_panel)) - # Add panel energy sensors (replacing synthetic ones) - # Filter out net energy sensors if disabled - panel_net_energy_enabled = config_entry.options.get(ENABLE_PANEL_NET_ENERGY_SENSORS, True) - - for description in PANEL_ENERGY_SENSORS: - # Skip net energy sensors if disabled - is_net_energy_sensor = "net_energy" in description.key or "NetEnergy" in description.key - - if not panel_net_energy_enabled and is_net_energy_sensor: - continue - entities.append(SpanPanelEnergySensor(coordinator, description, span_panel)) + # Add panel energy sensors — Gen2-only (energy history requires OpenAPI). + if PanelCapability.ENERGY_HISTORY in capabilities: + panel_net_energy_enabled = config_entry.options.get(ENABLE_PANEL_NET_ENERGY_SENSORS, True) + for description in PANEL_ENERGY_SENSORS: + is_net_energy_sensor = "net_energy" in description.key or "NetEnergy" in description.key + if not panel_net_energy_enabled and is_net_energy_sensor: + continue + entities.append(SpanPanelEnergySensor(coordinator, description, span_panel)) - # Add hardware status sensors (Door State, WiFi, Cellular, etc.) - for description_ss in STATUS_SENSORS: - entities.append(SpanPanelStatus(coordinator, description_ss, span_panel)) + # Add hardware status sensors (Door State, WiFi, Cellular, etc.) — Gen2-only. + if PanelCapability.HARDWARE_STATUS in capabilities: + for description_ss in STATUS_SENSORS: + entities.append(SpanPanelStatus(coordinator, description_ss, span_panel)) return entities @@ -87,20 +90,30 @@ def create_circuit_sensors( """Create circuit-level sensors for named circuits.""" entities: list[SpanCircuitPowerSensor | SpanCircuitEnergySensor] = [] + capabilities = span_panel.api.capabilities + has_energy_history = PanelCapability.ENERGY_HISTORY in capabilities + # Add circuit sensors for all named circuits (replacing synthetic ones) named_circuits = [cid for cid in span_panel.circuits if not cid.startswith("unmapped_tab_")] circuit_net_energy_enabled = config_entry.options.get(ENABLE_CIRCUIT_NET_ENERGY_SENSORS, True) for circuit_id in named_circuits: for circuit_description in CIRCUIT_SENSORS: - # Skip net energy sensors if disabled - is_net_energy_sensor = ( - "net_energy" in circuit_description.key or "energy_net" in circuit_description.key - ) + is_energy_sensor = circuit_description.key != "circuit_power" - if not circuit_net_energy_enabled and is_net_energy_sensor: + # Energy sensors require energy history capability (Gen2-only). + if is_energy_sensor and not has_energy_history: continue + # Skip net energy sensors if disabled via user option. + if is_energy_sensor: + is_net_energy_sensor = ( + "net_energy" in circuit_description.key + or "energy_net" in circuit_description.key + ) + if not circuit_net_energy_enabled and is_net_energy_sensor: + continue + if circuit_description.key == "circuit_power": # Use enhanced power sensor for power measurements entities.append( @@ -139,10 +152,13 @@ def create_unmapped_circuit_sensors( def create_battery_sensors( coordinator: SpanPanelCoordinator, span_panel: SpanPanel, config_entry: ConfigEntry ) -> list[SpanPanelBattery]: - """Create battery sensors if enabled.""" + """Create battery sensors if enabled and the panel supports battery data.""" entities: list[SpanPanelBattery] = [] - # Add battery sensor if enabled + # Battery data is only available on Gen2 panels. + if PanelCapability.BATTERY not in span_panel.api.capabilities: + return entities + battery_enabled = config_entry.options.get(BATTERY_ENABLE, False) if battery_enabled: entities.append(SpanPanelBattery(coordinator, BATTERY_SENSOR, span_panel)) @@ -153,9 +169,13 @@ def create_battery_sensors( def create_solar_sensors( coordinator: SpanPanelCoordinator, span_panel: SpanPanel, config_entry: ConfigEntry ) -> list[SpanSolarSensor | SpanSolarEnergySensor]: - """Create solar sensors if enabled and configured.""" + """Create solar sensors if enabled and the panel supports solar data.""" entities: list[SpanSolarSensor | SpanSolarEnergySensor] = [] + # Solar/feedthrough data is only available on Gen2 panels. + if PanelCapability.SOLAR not in span_panel.api.capabilities: + return entities + # Add solar sensors if enabled solar_enabled = config_entry.options.get(INVERTER_ENABLE, False) if not solar_enabled: diff --git a/custom_components/span_panel/span_panel_api.py b/custom_components/span_panel/span_panel_api.py index 14ae662..6ec3c5c 100644 --- a/custom_components/span_panel/span_panel_api.py +++ b/custom_components/span_panel/span_panel_api.py @@ -7,7 +7,7 @@ from typing import Any import uuid -from span_panel_api import SpanPanelClient, set_async_delay_func +from span_panel_api import PanelCapability, SpanPanelClient, set_async_delay_func from span_panel_api.exceptions import ( SpanPanelAPIError, SpanPanelAuthError, @@ -654,6 +654,18 @@ async def get_all_data(self, include_battery: bool = False) -> dict[str, Any]: _LOGGER.error("Failed to get all panel data: %s", e) raise + @property + def capabilities(self) -> PanelCapability: + """Return the panel's capabilities. + + Reads directly from the underlying client so the value reflects the + connected transport (GEN2_FULL for OpenAPI/HTTP, GEN3_INITIAL for gRPC). + Falls back to GEN2_FULL when the client has not yet been created. + """ + if self._client is not None: + return self._client.capabilities + return PanelCapability.GEN2_FULL + async def close(self) -> None: """Close the API client and clean up resources.""" _LOGGER.debug("[SpanPanelApi] Closing API client for host=%s", self.host) diff --git a/docs/dev/gen3-grpc-integration-plan.md b/docs/dev/gen3-grpc-integration-plan.md new file mode 100644 index 0000000..0b9eda7 --- /dev/null +++ b/docs/dev/gen3-grpc-integration-plan.md @@ -0,0 +1,286 @@ +# Gen3 gRPC Integration Plan + +## Status + +**Library work**: Complete on `span-panel-api` branch +[`grpc_addition`](https://github.com/SpanPanel/span-panel-api/tree/grpc_addition) — version **1.1.15**. + +**Integration work**: Phase 1 complete on branch `gen3-grpc-integration` — version **1.3.2**. +Phase 2 (push coordinator, Gen3 power-metric sensors) is deferred until Gen3 hardware +is available for testing. + +--- + +## Background + +PR #169 (`Griswoldlabs:gen3-grpc-support`) demonstrated Gen3 panel (MLO48 / +MAIN40) gRPC connectivity by placing transport-layer code directly inside the +integration under `custom_components/span_panel/gen3/`. Transport code +belongs in the `span-panel-api` library instead. + +The library's `grpc_addition` branch introduces: + +- `PanelCapability` flags for runtime feature advertisement +- `SpanPanelClientProtocol` + capability Protocol mixins for static type narrowing +- `SpanPanelSnapshot` / `SpanCircuitSnapshot` — transport-agnostic data models +- `SpanGrpcClient` — the Gen3 gRPC transport (migrated from PR #169's `gen3/`) +- `create_span_client()` — factory with auto-detection + +**Reference**: `span-panel-api/docs/Dev/grpc-transport-design.md` on the +`grpc_addition` branch contains the full transport-layer architecture and +interface specification. + +--- + +## Phase 1 — Completed Changes + +### 1. `const.py` — Add `CONF_PANEL_GENERATION` + +Added a single new constant for the config-entry key that stores the panel +generation chosen by the user or detected at setup time: + +```python +CONF_PANEL_GENERATION = "panel_generation" +``` + +Note: PR #169's `gen3/` directory was never present on this branch, so there +are no local generation constants to remove. `PanelGeneration` is available +from the library and used where needed (e.g. `async_step_gen3_setup`). + +### 2. `span_panel_api.py` — Expose `capabilities` Property + +Rather than restructuring `SpanPanelApi` to accept a protocol type (which +would break the existing init signature used throughout the integration), +a `capabilities` property is added that delegates directly to the underlying +client. This is the single authoritative source for capability data: + +```python +from span_panel_api import PanelCapability + +@property +def capabilities(self) -> PanelCapability: + """Return the panel's capabilities. + + Reads directly from the underlying client so the value reflects the + connected transport (GEN2_FULL for OpenAPI/HTTP, GEN3_INITIAL for gRPC). + Falls back to GEN2_FULL when the client has not yet been created. + """ + if self._client is not None: + return self._client.capabilities + return PanelCapability.GEN2_FULL +``` + +The `_client` attribute is still typed as `SpanPanelClient | None`. Full +migration to `SpanPanelClientProtocol` (accepting any transport) is deferred +to Phase 2 — it requires updating `_create_client()` to instantiate +`SpanGrpcClient` for Gen3 entries, which needs hardware to validate. + +### 3. `__init__.py` — Capability-Gated Platform Loading + +The static `PLATFORMS` list is replaced with a capability-driven build at +entry setup time. The loaded platform set is stored per entry so +`async_unload_entry` always unloads exactly what was loaded: + +```python +_BASE_PLATFORMS: list[Platform] = [ + Platform.BINARY_SENSOR, + Platform.SENSOR, +] + +_CAPABILITY_PLATFORMS: dict[PanelCapability, Platform] = { + PanelCapability.RELAY_CONTROL: Platform.SWITCH, + PanelCapability.PRIORITY_CONTROL: Platform.SELECT, +} + +_ACTIVE_PLATFORMS = "active_platforms" # key in hass.data per entry +``` + +In `async_setup_entry` (after `api.setup()` completes): + +```python +capabilities = span_panel.api.capabilities +active_platforms: list[Platform] = list(_BASE_PLATFORMS) +for cap, platform in _CAPABILITY_PLATFORMS.items(): + if cap in capabilities: + active_platforms.append(platform) + +hass.data[DOMAIN][entry.entry_id] = { + COORDINATOR: coordinator, + NAME: name, + _ACTIVE_PLATFORMS: active_platforms, +} + +await hass.config_entries.async_forward_entry_setups(entry, active_platforms) +``` + +In `async_unload_entry`: + +```python +entry_data = hass.data.get(DOMAIN, {}).get(entry.entry_id, {}) +active_platforms = entry_data.get(_ACTIVE_PLATFORMS, PLATFORMS) +unload_ok = await hass.config_entries.async_unload_platforms(entry, active_platforms) +``` + +**Gen2 result**: all four platforms loaded (`BINARY_SENSOR`, `SENSOR`, +`SWITCH`, `SELECT`) — identical to previous behaviour. + +**Gen3 result** (when `_create_client()` is updated in Phase 2): +`GEN3_INITIAL` = `PUSH_STREAMING` only → only `BINARY_SENSOR` + `SENSOR` +loaded; no switches, no selects. + +### 4. `config_flow.py` — Panel Generation Selector + +A dropdown is added to the initial user form so the user can select panel +generation before the first connection attempt: + +```python +vol.Optional(CONF_PANEL_GENERATION, default="auto"): selector({ + "select": { + "options": [ + {"value": "auto", "label": "Auto-detect"}, + {"value": "gen2", "label": "Gen2 (OpenAPI/HTTP)"}, + {"value": "gen3", "label": "Gen3 (gRPC)"}, + ], + "mode": "dropdown", + } +}) +``` + +Routing in `async_step_user`: + +- **gen2 / auto**: existing HTTP validation → `setup_flow()` → JWT auth steps + (unchanged behaviour). +- **gen3**: redirected to `async_step_gen3_setup`. + +`async_step_gen3_setup` probes port 50065 via `SpanGrpcClient`, retrieves the +serial number from `get_snapshot()`, sets an empty access token (Gen3 needs no +auth), then jumps directly to entity naming — bypassing all JWT auth steps. + +`CONF_PANEL_GENERATION` is stored in config entry `data` by `create_new_entry`: + +```python +data={ + CONF_HOST: host, + CONF_ACCESS_TOKEN: access_token, # empty string for Gen3 + CONF_USE_SSL: self.use_ssl, + CONF_PANEL_GENERATION: self.panel_generation, + "device_name": device_name, +}, +``` + +### 5. `sensors/factory.py` — Capability-Gated Sensor Creation + +Sensor groups that have no data on Gen3 are skipped when the corresponding +capability flag is absent. All gates read from `span_panel.api.capabilities`: + +| Sensor group | Capability gate | Always for Gen2? | +|--------------|-----------------|-----------------| +| DSM status sensors | `DSM_STATE` | ✅ | +| Panel power sensors | _(none — always created)_ | ✅ | +| Panel energy sensors | `ENERGY_HISTORY` | ✅ | +| Hardware status sensors (door, WiFi, cellular) | `HARDWARE_STATUS` | ✅ | +| Circuit power sensors | _(none — always created)_ | ✅ | +| Circuit energy sensors | `ENERGY_HISTORY` | ✅ | +| Battery sensor | `BATTERY` | ✅ (user option still applies) | +| Solar sensors | `SOLAR` | ✅ (user option still applies) | + +### 6. `manifest.json` — Version and Dependency + +```json +"requirements": ["span-panel-api[grpc]~=1.1.15"], +"version": "1.3.2" +``` + +--- + +## Phase 2 — Deferred (Requires Gen3 Hardware) + +The following items are designed and documented but require a real Gen3 panel +(MLO48 / MAIN40) to implement and validate: + +### Coordinator — Push vs. Poll + +```python +if PanelCapability.PUSH_STREAMING in caps: + coordinator = SpanPanelPushCoordinator(hass, api) +else: + coordinator = SpanPanelCoordinator(hass, api) +``` + +`SpanPanelPushCoordinator` calls `client.register_callback()` and triggers +`async_set_updated_data()` on each streaming notification. + +### `SpanPanelApi` — Accept Protocol, Not Concrete Class + +`_create_client()` needs to instantiate `SpanGrpcClient` when +`CONF_PANEL_GENERATION` is `"gen3"`, and `SpanPanelClient` otherwise. +The `_client` attribute should be widened to `SpanPanelClientProtocol | None` +once this is in place. + +### Sensors — Gen3 Power Metrics + +Gen3 exposes per-circuit `voltage_v`, `current_a`, `apparent_power_va`, +`reactive_power_var`, `frequency_hz`, `power_factor` via `SpanCircuitSnapshot`. +These become optional sensor entities, created only when the field is not `None` +in the first snapshot: + +```python +first_circuit = next(iter(snapshot.circuits.values()), None) +if first_circuit and first_circuit.voltage_v is not None: + entities.extend(build_gen3_circuit_sensors(snapshot)) +``` + +--- + +## Open Questions — Resolved + +| # | Question | Decision | +|---|----------|----------| +| 1 | Where to store detected generation? | `entry.data` as `CONF_PANEL_GENERATION` ✅ | +| 2 | Push coordinator: separate class or flag? | Separate class (`SpanPanelPushCoordinator`) — deferred Phase 2 | +| 3 | Gen3 sensors: existing file with capability-gates or new file? | Capability-gates in existing `sensors/factory.py` ✅ | +| 4 | `get_snapshot()` replace or coexist with individual Gen2 calls? | Coexist; `get_snapshot()` used only in Gen3 config-flow probe for now ✅ | +| 5 | Minimum `span-panel-api` version in `manifest.json`? | `~=1.1.15` with `[grpc]` extra ✅ | + +--- + +## Sequencing + +```text +[span-panel-api grpc_addition v1.1.15] ──── complete ────► + ↓ +[span gen3-grpc-integration v1.3.2] + Phase 1 complete: + ✅ const.py — CONF_PANEL_GENERATION + ✅ span_panel_api.py — capabilities property + ✅ __init__.py — capability-gated platform loading + ✅ config_flow.py — generation dropdown + async_step_gen3_setup + ✅ sensors/factory.py — capability-gated sensor groups + ✅ manifest.json — version 1.3.2, span-panel-api[grpc]~=1.1.15 + Phase 2 pending (Gen3 hardware required): + ⏳ coordinator.py — SpanPanelPushCoordinator + ⏳ span_panel_api.py — _create_client() Gen3 branch + ⏳ sensors/factory.py — Gen3 power-metric sensors + ↓ + → PR review +``` + +The library branch must be merged and published **before** this integration +branch can be merged, since the integration depends on the new library API. + +--- + +## Files Affected in This Integration + +| File | Change | Status | +|------|--------|--------| +| `custom_components/span_panel/const.py` | Added `CONF_PANEL_GENERATION` | ✅ Done | +| `custom_components/span_panel/span_panel_api.py` | Added `capabilities` property | ✅ Done | +| `custom_components/span_panel/__init__.py` | Capability-gated platform loading; store `active_platforms` per entry | ✅ Done | +| `custom_components/span_panel/config_flow.py` | Generation dropdown; `async_step_gen3_setup`; store generation in entry data | ✅ Done | +| `custom_components/span_panel/sensors/factory.py` | Capability-gated sensor groups (DSM, energy, hardware, battery, solar) | ✅ Done | +| `custom_components/span_panel/manifest.json` | Version 1.3.2; `span-panel-api[grpc]~=1.1.15` | ✅ Done | +| `custom_components/span_panel/coordinator.py` | Add `SpanPanelPushCoordinator` | ⏳ Phase 2 | +| `custom_components/span_panel/span_panel_api.py` | `_create_client()` Gen3 branch; widen `_client` to `SpanPanelClientProtocol` | ⏳ Phase 2 | +| `custom_components/span_panel/sensors/factory.py` | Gen3 power-metric sensor entities | ⏳ Phase 2 | +| `custom_components/span_panel/binary_sensor.py` | Gen3 binary sensors (None-guarded) | ⏳ Phase 2 | diff --git a/poetry.lock b/poetry.lock index 9a926e5..1116f8d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "acme" @@ -5137,7 +5137,7 @@ test = ["covdefaults (==2.3.0)", "pytest (==8.4.1)", "pytest-aiohttp (==1.1.0)", [[package]] name = "span-panel-api" -version = "1.1.14" +version = "1.1.15" description = "A client library for SPAN Panel API" optional = false python-versions = ">=3.10,<4.0" @@ -5148,11 +5148,15 @@ develop = true [package.dependencies] attrs = ">=22.2.0" click = ">=8.0.0" +grpcio = {version = ">=1.50.0", optional = true} httpx = ">=0.28.1,<0.29.0" numpy = ">=1.21.0" python-dateutil = ">=2.8.0" pyyaml = ">=6.0.0" +[package.extras] +grpc = ["grpcio (>=1.50.0)"] + [package.source] type = "directory" url = "../span-panel-api" @@ -6315,4 +6319,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.13.2,<3.14" -content-hash = "d2f773c82e16ff28e156c9f3cf068ed68b2d776c27a9c86f2210159f3bfecc2f" +content-hash = "a8310fcfa90db01e85d8b5bd8dc3327f67f97996e16d49e4d978e7c877471325" diff --git a/pyproject.toml b/pyproject.toml index 1b7b426..0017e18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ package-mode = false [tool.poetry.dependencies] python = ">=3.13.2,<3.14" homeassistant = "2025.12.4,<2026.0.0" # Pin to exact version for custom component compatibility -span-panel-api = {path = "../span-panel-api", develop = true} +span-panel-api = {path = "../span-panel-api", develop = true, extras = ["grpc"]} [tool.poetry.group.dev.dependencies] # Type stubs and dev-only tools that don't conflict with HA runtime diff --git a/scripts/sync-dependencies.py b/scripts/sync-dependencies.py index c639c51..f99f768 100755 --- a/scripts/sync-dependencies.py +++ b/scripts/sync-dependencies.py @@ -29,8 +29,8 @@ def get_manifest_versions(): for req in requirements: if req.startswith("span-panel-api"): - # Extract version from span-panel-api~=1.1.0 - match = re.search(r"span-panel-api[~=]+([0-9.]+)", req) + # Extract version from span-panel-api~=1.1.0 or span-panel-api[grpc]~=1.1.0 + match = re.search(r"span-panel-api(?:\[[^\]]+\])?[~=]+([0-9.]+)", req) if match: versions["span-panel-api"] = match.group(1) elif req.startswith("ha-synthetic-sensors"): From 8e0c7652852c2a573d58e031dc3832c2403af9ad Mon Sep 17 00:00:00 2001 From: cayossarian Date: Tue, 17 Feb 2026 12:54:11 -0800 Subject: [PATCH 02/13] docs: split Phase 2 into 2a (snapshot migration) and 2b (Gen3 runtime wiring) --- docs/dev/gen3-grpc-integration-plan.md | 176 +++++++++++++++++++++---- 1 file changed, 149 insertions(+), 27 deletions(-) diff --git a/docs/dev/gen3-grpc-integration-plan.md b/docs/dev/gen3-grpc-integration-plan.md index 0b9eda7..f5d4f34 100644 --- a/docs/dev/gen3-grpc-integration-plan.md +++ b/docs/dev/gen3-grpc-integration-plan.md @@ -193,36 +193,147 @@ capability flag is absent. All gates read from `span_panel.api.capabilities`: --- -## Phase 2 — Deferred (Requires Gen3 Hardware) +## Phase 2a — Snapshot Migration (Gen2 hardware, no Gen3 hardware required) -The following items are designed and documented but require a real Gen3 panel -(MLO48 / MAIN40) to implement and validate: +The current data path calls four individual API methods and maps OpenAPI-generated +types into integration domain objects. This phase migrates to `get_snapshot()` as +the single data-fetch call for both generations, eliminating any dependency on +OpenAPI types above `span-panel-api`. -### Coordinator — Push vs. Poll +### Why This Must Precede Phase 2b + +Phase 2b slots in `SpanGrpcClient` behind the same interface. If entity classes +still read from OpenAPI-backed properties, Gen3 data has nowhere to go. Completing +this migration means entities read from `SpanCircuitSnapshot` fields — the same +fields `SpanGrpcClient.get_snapshot()` populates — so Gen3 entities require no +additional changes for the metrics that both generations share. + +### `SpanPanelApi.update()` — call `get_snapshot()` instead of four methods + +Replace the four individual calls with one: + +```python +# Before +status = await self._client.get_status() +panel = await self._client.get_panel_state() +circs = await self._client.get_circuits() +batt = await self._client.get_storage_soe() + +# After +snapshot = await self._client.get_snapshot() +``` + +`SpanPanelClient.get_snapshot()` already makes those same four calls internally — +this is a refactor, not a behaviour change. + +### `SpanPanel` — populate from `SpanPanelSnapshot` + +`SpanPanel` currently holds the four OpenAPI response objects. Replace with a +single `SpanPanelSnapshot` (or equivalent fields derived from it): + +```python +self._snapshot = snapshot # SpanPanelSnapshot +self.main_power_w = snapshot.main_power_w +self.grid_power_w = snapshot.grid_power_w +self.battery_soe = snapshot.battery_soe +self.dsm_state = snapshot.dsm_state +# ... etc. +``` + +### `SpanPanelCircuit` — wrap `SpanCircuitSnapshot` + +`SpanPanelCircuit` currently wraps the OpenAPI `Circuit` type. Redirect it to +wrap `SpanCircuitSnapshot` instead, mapping field names as needed: + +| Old (`Circuit` field) | New (`SpanCircuitSnapshot` field) | +|-----------------------|----------------------------------| +| `instantPowerW` | `power_w` | +| `name` | `name` | +| `relayState` | `relay_state` | +| `priority` | `priority` | +| `tabs` | `tabs` | +| `energyAccumImportWh` | `energy_consumed_wh` | +| `energyAccumExportWh` | `energy_produced_wh` | + +Entity classes need no changes — they continue reading from `SpanPanelCircuit` +properties. Only the backing field source changes. + +--- + +## Phase 2b — Gen3 Runtime Wiring (Gen3 hardware required) + +Depends on Phase 2a being complete. The entity data path must already read from +`SpanCircuitSnapshot`-backed properties before Gen3 data can flow through it. + +### `SpanPanelApi._create_client()` — Gen3 branch + +```python +def _create_client(self) -> SpanPanelClientProtocol: + if self._config.get(CONF_PANEL_GENERATION) == "gen3": + from span_panel_api.grpc import SpanGrpcClient # pylint: disable=import-outside-toplevel + from span_panel_api.grpc.const import DEFAULT_GRPC_PORT # pylint: disable=import-outside-toplevel + return SpanGrpcClient(host=self._host, port=DEFAULT_GRPC_PORT) + return SpanPanelClient(host=self._host, ...) +``` + +`_client` is widened to `SpanPanelClientProtocol | None`. All callers that +currently rely on `SpanPanelClient`-specific methods (authenticate, etc.) must +be guarded by `isinstance(self._client, AuthCapableProtocol)`. + +### `coordinator.py` — `SpanPanelPushCoordinator` + +```python +class SpanPanelPushCoordinator(DataUpdateCoordinator): + async def async_setup(self) -> None: + assert isinstance(self._api.client, StreamingCapableProtocol) + self._unsub = self._api.client.register_callback(self._on_push) + await self._api.client.start_streaming() + + def _on_push(self) -> None: + # get_snapshot() on Gen3 is a cheap in-memory conversion — no I/O + snapshot = asyncio.get_event_loop().run_until_complete( + self._api.client.get_snapshot() + ) + self.async_set_updated_data(snapshot) + + async def async_teardown(self) -> None: + if self._unsub: + self._unsub() + await self._api.client.stop_streaming() +``` + +### `__init__.py` — choose coordinator at setup time ```python +caps = span_panel.api.capabilities if PanelCapability.PUSH_STREAMING in caps: - coordinator = SpanPanelPushCoordinator(hass, api) + coordinator = SpanPanelPushCoordinator(hass, span_panel.api) + await coordinator.async_setup() else: - coordinator = SpanPanelCoordinator(hass, api) + coordinator = SpanPanelCoordinator(hass, span_panel.api) + await coordinator.async_config_entry_first_refresh() ``` -`SpanPanelPushCoordinator` calls `client.register_callback()` and triggers -`async_set_updated_data()` on each streaming notification. +### `SpanGrpcClient` hardware validation -### `SpanPanelApi` — Accept Protocol, Not Concrete Class +Before wiring the runtime path, validate the library client against a real panel: -`_create_client()` needs to instantiate `SpanGrpcClient` when -`CONF_PANEL_GENERATION` is `"gen3"`, and `SpanPanelClient` otherwise. -The `_client` attribute should be widened to `SpanPanelClientProtocol | None` -once this is in place. +- `connect()` populates circuits with correct IIDs and names +- `Subscribe` stream delivers notifications at expected cadence +- `_decode_circuit_metrics()` produces correct power/voltage values +- Dual-phase circuit detection works correctly +- `get_snapshot()` returns consistent data during active streaming -### Sensors — Gen3 Power Metrics +See `span-panel-api/docs/Dev/grpc-transport-design.md` → "Hardware Validation +Required" for the full validation checklist. + +### Sensors — Gen3-only power metrics Gen3 exposes per-circuit `voltage_v`, `current_a`, `apparent_power_va`, -`reactive_power_var`, `frequency_hz`, `power_factor` via `SpanCircuitSnapshot`. -These become optional sensor entities, created only when the field is not `None` -in the first snapshot: +`reactive_power_var`, `frequency_hz`, `power_factor` — fields that have no Gen2 +equivalent. These are added as new entity classes that read directly from +`SpanCircuitSnapshot`, created only when the field is not `None` in the first +snapshot: ```python first_circuit = next(iter(snapshot.circuits.values()), None) @@ -230,17 +341,21 @@ if first_circuit and first_circuit.voltage_v is not None: entities.extend(build_gen3_circuit_sensors(snapshot)) ``` +Sensors that exist on both generations (circuit power, panel power) need no new +entity classes — after Phase 2a they already read from `SpanCircuitSnapshot`. + --- -## Open Questions — Resolved +## Open Questions | # | Question | Decision | |---|----------|----------| | 1 | Where to store detected generation? | `entry.data` as `CONF_PANEL_GENERATION` ✅ | -| 2 | Push coordinator: separate class or flag? | Separate class (`SpanPanelPushCoordinator`) — deferred Phase 2 | +| 2 | Push coordinator: separate class or flag? | Separate class (`SpanPanelPushCoordinator`) — Phase 2b ⏳ | | 3 | Gen3 sensors: existing file with capability-gates or new file? | Capability-gates in existing `sensors/factory.py` ✅ | -| 4 | `get_snapshot()` replace or coexist with individual Gen2 calls? | Coexist; `get_snapshot()` used only in Gen3 config-flow probe for now ✅ | +| 4 | `get_snapshot()` replace or coexist with individual Gen2 calls? | Replace — `update()` calls `get_snapshot()` exclusively (Phase 2a) ⏳ | | 5 | Minimum `span-panel-api` version in `manifest.json`? | `~=1.1.15` with `[grpc]` extra ✅ | +| 6 | Gen3 entity architecture: separate classes, translation layer, or shared base? | Shared base via `SpanCircuitSnapshot` (Phase 2a migration eliminates the choice — existing entity classes work unchanged for overlapping metrics) ✅ | --- @@ -257,10 +372,15 @@ if first_circuit and first_circuit.voltage_v is not None: ✅ config_flow.py — generation dropdown + async_step_gen3_setup ✅ sensors/factory.py — capability-gated sensor groups ✅ manifest.json — version 1.3.2, span-panel-api[grpc]~=1.1.15 - Phase 2 pending (Gen3 hardware required): + Phase 2a pending (Gen2 hardware sufficient): + ⏳ span_panel_api.py — update() calls get_snapshot() + ⏳ span_panel.py — populate from SpanPanelSnapshot + ⏳ span_panel_circuit.py — wrap SpanCircuitSnapshot instead of OpenAPI Circuit + Phase 2b pending (Gen3 hardware required, depends on 2a): + ⏳ span_panel_api.py — _create_client() Gen3 branch; widen _client type ⏳ coordinator.py — SpanPanelPushCoordinator - ⏳ span_panel_api.py — _create_client() Gen3 branch - ⏳ sensors/factory.py — Gen3 power-metric sensors + ⏳ __init__.py — coordinator selection by capability + ⏳ sensors/factory.py — Gen3-only sensor entities (voltage, current, etc.) ↓ → PR review ``` @@ -280,7 +400,9 @@ branch can be merged, since the integration depends on the new library API. | `custom_components/span_panel/config_flow.py` | Generation dropdown; `async_step_gen3_setup`; store generation in entry data | ✅ Done | | `custom_components/span_panel/sensors/factory.py` | Capability-gated sensor groups (DSM, energy, hardware, battery, solar) | ✅ Done | | `custom_components/span_panel/manifest.json` | Version 1.3.2; `span-panel-api[grpc]~=1.1.15` | ✅ Done | -| `custom_components/span_panel/coordinator.py` | Add `SpanPanelPushCoordinator` | ⏳ Phase 2 | -| `custom_components/span_panel/span_panel_api.py` | `_create_client()` Gen3 branch; widen `_client` to `SpanPanelClientProtocol` | ⏳ Phase 2 | -| `custom_components/span_panel/sensors/factory.py` | Gen3 power-metric sensor entities | ⏳ Phase 2 | -| `custom_components/span_panel/binary_sensor.py` | Gen3 binary sensors (None-guarded) | ⏳ Phase 2 | +| `custom_components/span_panel/span_panel_api.py` | `update()` calls `get_snapshot()`; widen `_client` to protocol type | ⏳ Phase 2a/2b | +| `custom_components/span_panel/span_panel.py` | Populate from `SpanPanelSnapshot` instead of OpenAPI types | ⏳ Phase 2a | +| `custom_components/span_panel/span_panel_circuit.py` | Wrap `SpanCircuitSnapshot` instead of OpenAPI `Circuit` | ⏳ Phase 2a | +| `custom_components/span_panel/coordinator.py` | Add `SpanPanelPushCoordinator`; coordinator selection in setup | ⏳ Phase 2b | +| `custom_components/span_panel/__init__.py` | Coordinator selection by `PUSH_STREAMING` capability | ⏳ Phase 2b | +| `custom_components/span_panel/sensors/factory.py` | Gen3-only sensor entities (voltage, current, apparent/reactive power, frequency, power factor) | ⏳ Phase 2b | From 718d834e37ee20c61a8ced11b2324bba73b62d71 Mon Sep 17 00:00:00 2001 From: cayossarian Date: Tue, 17 Feb 2026 13:20:04 -0800 Subject: [PATCH 03/13] Phase 2a: migrate SpanPanel.update() to single get_snapshot() call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the four-call get_all_data() fetch with a single get_snapshot() call that returns a unified SpanPanelSnapshot. Each domain object now has a from_snapshot() factory that maps snapshot fields to its internal structure — enabling Gen3 data to flow through the same entity classes as Gen2 for the metrics they share. Changes: - span_panel.py: update() calls api.get_snapshot() and passes the snapshot to the four from_snapshot() factories atomically - span_panel_api.py: add get_snapshot() delegating to client.get_snapshot() with the same error handling pattern as other API methods - span_panel_circuit.py: add from_snapshot(SpanCircuitSnapshot) - span_panel_data.py: add from_snapshot(SpanPanelSnapshot) - span_panel_hardware_status.py: add from_snapshot(SpanPanelSnapshot) - span_panel_storage_battery.py: add from_snapshot(SpanPanelSnapshot) - tests/conftest.py: register real SpanPanelSnapshot/SpanCircuitSnapshot on the mock so test fixtures can construct real snapshot instances - tests/test_span_panel.py: rewrite TestSpanPanelUpdate to use get_snapshot() mock returning a real SpanPanelSnapshot fixture --- custom_components/span_panel/span_panel.py | 36 ++--- .../span_panel/span_panel_api.py | 38 +++++- .../span_panel/span_panel_circuit.py | 32 +++++ .../span_panel/span_panel_data.py | 25 ++++ .../span_panel/span_panel_hardware_status.py | 27 ++++ .../span_panel/span_panel_storage_battery.py | 8 ++ tests/conftest.py | 15 ++ tests/test_span_panel.py | 129 ++++++++++-------- 8 files changed, 226 insertions(+), 84 deletions(-) diff --git a/custom_components/span_panel/span_panel.py b/custom_components/span_panel/span_panel.py index 2da0206..3be790b 100644 --- a/custom_components/span_panel/span_panel.py +++ b/custom_components/span_panel/span_panel.py @@ -113,41 +113,27 @@ def _update_storage_battery(self, new_battery: SpanPanelStorageBattery) -> None: self._storage_battery = new_battery async def update(self) -> None: - """Update all panel data atomically.""" + """Update all panel data atomically from a single get_snapshot() call.""" try: - # Start timing for API calls api_start = _epoch_time() - # Debug battery option status battery_option_enabled = self._options and self._options.enable_battery_percentage - # Use batch API call for true parallelization at the client level - # This makes concurrent HTTP requests when cache misses occur - all_data = await self.api.get_all_data(include_battery=bool(battery_option_enabled)) + snapshot = await self.api.get_snapshot() - # Extract processed data from batch response - new_status = all_data.get("status") - new_panel = all_data.get("panel") - new_circuits = all_data.get("circuits") - new_battery = all_data.get("battery") if battery_option_enabled else None - - # Atomic updates - ensure we have the required data - if new_status is not None: - self._update_status(new_status) - if new_panel is not None: - self._update_panel(new_panel) - if new_circuits is not None: - self._update_circuits(new_circuits) + self._update_status(SpanPanelHardwareStatus.from_snapshot(snapshot)) + self._update_panel(SpanPanelData.from_snapshot(snapshot)) + self._update_circuits( + {cid: SpanPanelCircuit.from_snapshot(cs) for cid, cs in snapshot.circuits.items()} + ) - if new_battery is not None: - self._update_storage_battery(new_battery) + if battery_option_enabled: + self._update_storage_battery(SpanPanelStorageBattery.from_snapshot(snapshot)) api_duration = _epoch_time() - api_start - - # INFO level logging for API call performance - _LOGGER.info("Panel API calls completed (CLIENT-PARALLEL) - Total: %.3fs", api_duration) - + _LOGGER.info("Panel API calls completed (SNAPSHOT) - Total: %.3fs", api_duration) _LOGGER.debug("Panel update completed successfully") + except SpanPanelReturnedEmptyData: _LOGGER.warning("Span Panel returned empty data") except SpanPanelSimulationOfflineError: # Debug logged in coordinator.py diff --git a/custom_components/span_panel/span_panel_api.py b/custom_components/span_panel/span_panel_api.py index 6ec3c5c..bdd4bb5 100644 --- a/custom_components/span_panel/span_panel_api.py +++ b/custom_components/span_panel/span_panel_api.py @@ -7,7 +7,7 @@ from typing import Any import uuid -from span_panel_api import PanelCapability, SpanPanelClient, set_async_delay_func +from span_panel_api import PanelCapability, SpanPanelClient, SpanPanelSnapshot, set_async_delay_func from span_panel_api.exceptions import ( SpanPanelAPIError, SpanPanelAuthError, @@ -527,6 +527,42 @@ async def get_storage_battery_data(self) -> SpanPanelStorageBattery: _LOGGER.error("Failed to get storage battery data: %s", e) raise + async def get_snapshot(self) -> SpanPanelSnapshot: + """Get a unified, transport-agnostic snapshot of the current panel state. + + This is the primary data-fetch method for all panel generations. + Individual API methods (get_status_data, get_panel_data, etc.) are + preserved for legacy callers but should not be used by new code. + """ + if self._is_panel_offline(): + raise SpanPanelSimulationOfflineError("Panel is offline in simulation mode") + + self._ensure_client_open() + if self._client is None: + raise SpanPanelAPIError("API client has been closed") + self._debug_check_client("get_snapshot") + try: + await self._ensure_authenticated() + return await self._client.get_snapshot() + + except SpanPanelRetriableError as e: + _LOGGER.warning("Retriable error getting snapshot (will retry): %s", e) + raise + except SpanPanelServerError as e: + _LOGGER.error("Server error getting snapshot (will not retry): %s", e) + raise + except SpanPanelAuthError as e: + self._authenticated = False + _LOGGER.error("Authentication failed for snapshot: %s", e) + raise + except ( + SpanPanelConnectionError, + SpanPanelTimeoutError, + SpanPanelAPIError, + ) as e: + _LOGGER.error("Failed to get snapshot: %s", e) + raise + async def set_relay(self, circuit: SpanPanelCircuit, state: CircuitRelayState) -> None: """Set the relay state.""" self._ensure_client_open() diff --git a/custom_components/span_panel/span_panel_circuit.py b/custom_components/span_panel/span_panel_circuit.py index 04280ff..5ba2144 100644 --- a/custom_components/span_panel/span_panel_circuit.py +++ b/custom_components/span_panel/span_panel_circuit.py @@ -4,6 +4,8 @@ from dataclasses import dataclass, field from typing import Any +from span_panel_api import SpanCircuitSnapshot + from .const import CircuitRelayState @@ -66,6 +68,36 @@ def from_dict(data: dict[str, Any]) -> "SpanPanelCircuit": raw_data=data_copy, ) + @staticmethod + def from_snapshot(snapshot: SpanCircuitSnapshot) -> "SpanPanelCircuit": + """Create a SpanPanelCircuit from a transport-agnostic SpanCircuitSnapshot. + + Used by both Gen2 (via get_snapshot()) and Gen3 transports. + Fields absent from Gen3 snapshots default to neutral values; they are + only accessed by capability-gated entity classes that won't be created + for Gen3 panels. + """ + relay = snapshot.relay_state + return SpanPanelCircuit( + circuit_id=snapshot.circuit_id, + name=snapshot.name, + relay_state=relay if relay is not None else CircuitRelayState.CLOSED.name, + instant_power=snapshot.power_w, + instant_power_update_time=0, + produced_energy=snapshot.energy_produced_wh + if snapshot.energy_produced_wh is not None + else 0.0, + consumed_energy=snapshot.energy_consumed_wh + if snapshot.energy_consumed_wh is not None + else 0.0, + energy_accum_update_time=0, + tabs=list(snapshot.tabs) if snapshot.tabs is not None else [], + priority=snapshot.priority if snapshot.priority is not None else "MUST_HAVE", + is_user_controllable=relay is not None, + is_sheddable=False, + is_never_backup=False, + ) + def copy(self) -> "SpanPanelCircuit": """Create a deep copy for atomic operations.""" # Circuit contains nested mutable objects, use deepcopy diff --git a/custom_components/span_panel/span_panel_data.py b/custom_components/span_panel/span_panel_data.py index 4742665..d2dd5af 100644 --- a/custom_components/span_panel/span_panel_data.py +++ b/custom_components/span_panel/span_panel_data.py @@ -4,6 +4,8 @@ from dataclasses import dataclass, field from typing import Any +from span_panel_api import SpanPanelSnapshot + from .const import ( CURRENT_RUN_CONFIG, DSM_GRID_STATE, @@ -65,6 +67,29 @@ def from_dict(cls, data: dict[str, Any], options: Options | None = None) -> "Spa return cls(**common_data) + @classmethod + def from_snapshot(cls, snapshot: SpanPanelSnapshot) -> "SpanPanelData": + """Create a SpanPanelData from a transport-agnostic snapshot. + + Gen3 panels only supply main_power_w; all Gen2-only fields default to + zero or empty string. Those fields are only read by entity classes that + are gated behind ENERGY_HISTORY, DSM_STATE, or SOLAR capabilities. + """ + return cls( + main_relay_state=snapshot.main_relay_state or "UNKNOWN", + main_meter_energy_produced=snapshot.main_meter_energy_produced_wh or 0.0, + main_meter_energy_consumed=snapshot.main_meter_energy_consumed_wh or 0.0, + instant_grid_power=snapshot.main_power_w, + feedthrough_power=snapshot.feedthrough_power_w or 0.0, + feedthrough_energy_produced=snapshot.feedthrough_energy_produced_wh or 0.0, + feedthrough_energy_consumed=snapshot.feedthrough_energy_consumed_wh or 0.0, + grid_sample_start_ms=0, + grid_sample_end_ms=0, + dsm_grid_state=snapshot.dsm_grid_state or "", + dsm_state=snapshot.dsm_state or "", + current_run_config=snapshot.current_run_config or "", + ) + def copy(self) -> "SpanPanelData": """Create a deep copy for atomic operations.""" return deepcopy(self) diff --git a/custom_components/span_panel/span_panel_hardware_status.py b/custom_components/span_panel/span_panel_hardware_status.py index 0605ec5..7ae67a5 100644 --- a/custom_components/span_panel/span_panel_hardware_status.py +++ b/custom_components/span_panel/span_panel_hardware_status.py @@ -5,6 +5,8 @@ import logging from typing import Any +from span_panel_api import SpanPanelSnapshot + from .const import SYSTEM_DOOR_STATE_CLOSED, SYSTEM_DOOR_STATE_OPEN _LOGGER = logging.getLogger(__name__) @@ -81,6 +83,31 @@ def from_dict(cls, data: dict[str, Any]) -> "SpanPanelHardwareStatus": _system_data=system_data, ) + @classmethod + def from_snapshot(cls, snapshot: SpanPanelSnapshot) -> "SpanPanelHardwareStatus": + """Create a SpanPanelHardwareStatus from a transport-agnostic snapshot. + + Gen2 panels populate all hardware status fields. Gen3 panels only + populate serial_number and firmware_version; all other fields default + to None or False because the corresponding entity classes are gated + behind PanelCapability.HARDWARE_STATUS and will not be created. + """ + return cls( + firmware_version=snapshot.firmware_version, + update_status=snapshot.hardware_update_status or "", + env=snapshot.hardware_env or "", + manufacturer=snapshot.hardware_manufacturer or "", + serial_number=snapshot.serial_number, + model=snapshot.hardware_model or "", + door_state=snapshot.hardware_door_state, + uptime=snapshot.hardware_uptime or 0, + is_ethernet_connected=snapshot.hardware_is_ethernet_connected or False, + is_wifi_connected=snapshot.hardware_is_wifi_connected or False, + is_cellular_connected=snapshot.hardware_is_cellular_connected or False, + proximity_proven=snapshot.hardware_proximity_proven, + remaining_auth_unlock_button_presses=0, + ) + def copy(self) -> "SpanPanelHardwareStatus": """Create a deep copy of hardware status.""" return deepcopy(self) diff --git a/custom_components/span_panel/span_panel_storage_battery.py b/custom_components/span_panel/span_panel_storage_battery.py index 2edbe3f..6c68617 100644 --- a/custom_components/span_panel/span_panel_storage_battery.py +++ b/custom_components/span_panel/span_panel_storage_battery.py @@ -3,6 +3,8 @@ from dataclasses import dataclass, field from typing import Any +from span_panel_api import SpanPanelSnapshot + @dataclass class SpanPanelStorageBattery: @@ -16,3 +18,9 @@ class SpanPanelStorageBattery: def from_dict(data: dict[str, Any]) -> "SpanPanelStorageBattery": """Read the data from the dictionary.""" return SpanPanelStorageBattery(storage_battery_percentage=data.get("percentage", 0)) + + @staticmethod + def from_snapshot(snapshot: SpanPanelSnapshot) -> "SpanPanelStorageBattery": + """Create a SpanPanelStorageBattery from a transport-agnostic snapshot.""" + percentage = int(snapshot.battery_soe) if snapshot.battery_soe is not None else 0 + return SpanPanelStorageBattery(storage_battery_percentage=percentage) diff --git a/tests/conftest.py b/tests/conftest.py index bf8f151..2b71ab1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,11 +14,26 @@ # sys.path.insert(0, str(Path(__file__).parent.parent)) # Removed - using pytest pythonpath instead +# Import real model classes before the mock is installed so that snapshot-based +# tests can construct real SpanPanelSnapshot / SpanCircuitSnapshot instances. +from span_panel_api.models import PanelCapability as _PanelCapability # noqa: E402 +from span_panel_api.models import PanelGeneration as _PanelGeneration # noqa: E402 +from span_panel_api.models import SpanCircuitSnapshot as _SpanCircuitSnapshot # noqa: E402 +from span_panel_api.models import SpanPanelSnapshot as _SpanPanelSnapshot # noqa: E402 + # Mock span_panel_api before importing custom_components # Create mock modules for span_panel_api span_panel_api_mock = MagicMock() span_panel_api_exceptions_mock = MagicMock() +# Register real model classes on the mock so imports like +# from span_panel_api import SpanPanelSnapshot +# return the actual dataclass, not a MagicMock. +span_panel_api_mock.PanelCapability = _PanelCapability +span_panel_api_mock.PanelGeneration = _PanelGeneration +span_panel_api_mock.SpanCircuitSnapshot = _SpanCircuitSnapshot +span_panel_api_mock.SpanPanelSnapshot = _SpanPanelSnapshot + # Create proper mock exception classes that maintain distinct types class MockSpanPanelError(Exception): """Base mock exception.""" diff --git a/tests/test_span_panel.py b/tests/test_span_panel.py index dbe1d36..9b451f7 100644 --- a/tests/test_span_panel.py +++ b/tests/test_span_panel.py @@ -4,6 +4,8 @@ import pytest +from span_panel_api import PanelGeneration, SpanCircuitSnapshot, SpanPanelSnapshot + from custom_components.span_panel.exceptions import SpanPanelReturnedEmptyData from custom_components.span_panel.options import Options from custom_components.span_panel.span_panel import SpanPanel @@ -56,6 +58,49 @@ def mock_battery(): return MagicMock(spec=SpanPanelStorageBattery) +@pytest.fixture +def minimal_snapshot(): + """Create a minimal SpanPanelSnapshot for update() tests.""" + return SpanPanelSnapshot( + panel_generation=PanelGeneration.GEN2, + serial_number="SPAN123", + firmware_version="1.2.3", + main_power_w=500.0, + main_relay_state="CLOSED", + grid_power_w=500.0, + dsm_state="DSM_GRID_OK", + dsm_grid_state="DSM_ON_GRID", + current_run_config="PANEL_ON_GRID", + battery_soe=75.0, + feedthrough_power_w=0.0, + hardware_door_state="CLOSED", + hardware_uptime=12345, + hardware_is_ethernet_connected=True, + hardware_is_wifi_connected=False, + hardware_is_cellular_connected=False, + hardware_update_status="UP_TO_DATE", + hardware_env="prod", + hardware_manufacturer="Span", + hardware_model="MAIN40", + hardware_proximity_proven=True, + circuits={ + "circuit_1": SpanCircuitSnapshot( + circuit_id="circuit_1", + name="Washer", + power_w=200.0, + voltage_v=120.0, + current_a=1.67, + is_on=True, + relay_state="CLOSED", + priority="MUST_HAVE", + tabs=[1], + energy_produced_wh=0.0, + energy_consumed_wh=5000.0, + ) + }, + ) + + class TestSpanPanelInit: """Test SpanPanel initialization.""" @@ -161,93 +206,62 @@ class TestSpanPanelUpdate: """Test SpanPanel update functionality.""" @pytest.mark.asyncio - async def test_update_success_without_battery( - self, mock_status, mock_panel_data, mock_circuits - ): - """Test successful update without battery data.""" - panel = SpanPanel("192.168.1.100") + async def test_update_success_without_battery(self, minimal_snapshot): + """Test successful update without battery data (battery option disabled).""" + panel = SpanPanel("192.168.1.100") # no options → battery disabled - # Mock API methods - panel.api.get_all_data = AsyncMock(return_value={ - "status": mock_status, - "panel": mock_panel_data, - "circuits": mock_circuits, - "battery": None - }) + panel.api.get_snapshot = AsyncMock(return_value=minimal_snapshot) await panel.update() - # Verify API calls - panel.api.get_all_data.assert_called_once_with(include_battery=False) + panel.api.get_snapshot.assert_called_once() - # Verify data is updated - assert panel._status is mock_status - assert panel._panel is mock_panel_data - assert panel._circuits is mock_circuits - assert panel._storage_battery is None + # Domain objects are derived from the snapshot + assert panel._status is not None + assert panel._status.serial_number == "SPAN123" + assert panel._panel is not None + assert panel._panel.instant_grid_power == 500.0 + assert "circuit_1" in panel._circuits + assert panel._circuits["circuit_1"].instant_power == 200.0 + assert panel._storage_battery is None # battery option not enabled @pytest.mark.asyncio - async def test_update_success_with_battery( - self, mock_status, mock_panel_data, mock_circuits, mock_battery, mock_options - ): - """Test successful update with battery data.""" - panel = SpanPanel("192.168.1.100", options=mock_options) + async def test_update_success_with_battery(self, minimal_snapshot, mock_options): + """Test successful update with battery data when option is enabled.""" + panel = SpanPanel("192.168.1.100", options=mock_options) # battery enabled - # Mock API methods - panel.api.get_all_data = AsyncMock(return_value={ - "status": mock_status, - "panel": mock_panel_data, - "circuits": mock_circuits, - "battery": mock_battery - }) + panel.api.get_snapshot = AsyncMock(return_value=minimal_snapshot) await panel.update() - # Verify API calls - panel.api.get_all_data.assert_called_once_with(include_battery=True) + panel.api.get_snapshot.assert_called_once() - # Verify data is updated - assert panel._status is mock_status - assert panel._panel is mock_panel_data - assert panel._circuits is mock_circuits - assert panel._storage_battery is mock_battery + assert panel._storage_battery is not None + assert panel._storage_battery.storage_battery_percentage == 75 # from battery_soe=75.0 @pytest.mark.asyncio - async def test_update_with_battery_disabled(self, mock_status, mock_panel_data, mock_circuits): + async def test_update_with_battery_disabled(self, minimal_snapshot): """Test update when battery is disabled in options.""" options = MagicMock(spec=Options) options.enable_battery_percentage = False - options.enable_solar_sensors = False - options.inverter_leg1 = 0 - options.inverter_leg2 = 0 options.api_retries = 3 options.api_retry_timeout = 5.0 options.api_retry_backoff_multiplier = 2.0 panel = SpanPanel("192.168.1.100", options=options) - # Mock API methods - panel.api.get_all_data = AsyncMock(return_value={ - "status": mock_status, - "panel": mock_panel_data, - "circuits": mock_circuits, - "battery": None - }) + panel.api.get_snapshot = AsyncMock(return_value=minimal_snapshot) await panel.update() - # Verify API calls - panel.api.get_all_data.assert_called_once_with(include_battery=False) + panel.api.get_snapshot.assert_called_once() assert panel._storage_battery is None @pytest.mark.asyncio - async def test_update_handles_empty_data_exception( - self, mock_status, mock_panel_data, mock_circuits - ): + async def test_update_handles_empty_data_exception(self): """Test update handles SpanPanelReturnedEmptyData exception.""" panel = SpanPanel("192.168.1.100") - # Mock API methods - raises empty data exception - panel.api.get_all_data = AsyncMock(side_effect=SpanPanelReturnedEmptyData("Empty data")) + panel.api.get_snapshot = AsyncMock(side_effect=SpanPanelReturnedEmptyData("Empty data")) # Should not raise exception, just log warning await panel.update() @@ -262,8 +276,7 @@ async def test_update_propagates_other_exceptions(self): """Test update propagates non-empty-data exceptions.""" panel = SpanPanel("192.168.1.100") - # Mock API method to raise generic exception - panel.api.get_all_data = AsyncMock(side_effect=Exception("API error")) + panel.api.get_snapshot = AsyncMock(side_effect=Exception("API error")) with pytest.raises(Exception, match="API error"): await panel.update() From a6e62d1da3ce6fb0fc6e8888539ba4f2952c5e7c Mon Sep 17 00:00:00 2001 From: cayossarian Date: Tue, 17 Feb 2026 13:21:15 -0800 Subject: [PATCH 04/13] docs: mark Phase 2a complete in gen3-grpc-integration-plan --- docs/dev/gen3-grpc-integration-plan.md | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/docs/dev/gen3-grpc-integration-plan.md b/docs/dev/gen3-grpc-integration-plan.md index f5d4f34..25b3cf4 100644 --- a/docs/dev/gen3-grpc-integration-plan.md +++ b/docs/dev/gen3-grpc-integration-plan.md @@ -5,8 +5,8 @@ **Library work**: Complete on `span-panel-api` branch [`grpc_addition`](https://github.com/SpanPanel/span-panel-api/tree/grpc_addition) — version **1.1.15**. -**Integration work**: Phase 1 complete on branch `gen3-grpc-integration` — version **1.3.2**. -Phase 2 (push coordinator, Gen3 power-metric sensors) is deferred until Gen3 hardware +**Integration work**: Phase 1 and Phase 2a complete on branch `gen3-grpc-integration` — version **1.3.2**. +Phase 2b (push coordinator, Gen3 power-metric sensors) is deferred until Gen3 hardware is available for testing. --- @@ -353,7 +353,7 @@ entity classes — after Phase 2a they already read from `SpanCircuitSnapshot`. | 1 | Where to store detected generation? | `entry.data` as `CONF_PANEL_GENERATION` ✅ | | 2 | Push coordinator: separate class or flag? | Separate class (`SpanPanelPushCoordinator`) — Phase 2b ⏳ | | 3 | Gen3 sensors: existing file with capability-gates or new file? | Capability-gates in existing `sensors/factory.py` ✅ | -| 4 | `get_snapshot()` replace or coexist with individual Gen2 calls? | Replace — `update()` calls `get_snapshot()` exclusively (Phase 2a) ⏳ | +| 4 | `get_snapshot()` replace or coexist with individual Gen2 calls? | Replace — `update()` calls `get_snapshot()` exclusively (Phase 2a) ✅ | | 5 | Minimum `span-panel-api` version in `manifest.json`? | `~=1.1.15` with `[grpc]` extra ✅ | | 6 | Gen3 entity architecture: separate classes, translation layer, or shared base? | Shared base via `SpanCircuitSnapshot` (Phase 2a migration eliminates the choice — existing entity classes work unchanged for overlapping metrics) ✅ | @@ -372,10 +372,13 @@ entity classes — after Phase 2a they already read from `SpanCircuitSnapshot`. ✅ config_flow.py — generation dropdown + async_step_gen3_setup ✅ sensors/factory.py — capability-gated sensor groups ✅ manifest.json — version 1.3.2, span-panel-api[grpc]~=1.1.15 - Phase 2a pending (Gen2 hardware sufficient): - ⏳ span_panel_api.py — update() calls get_snapshot() - ⏳ span_panel.py — populate from SpanPanelSnapshot - ⏳ span_panel_circuit.py — wrap SpanCircuitSnapshot instead of OpenAPI Circuit + Phase 2a complete: + ✅ span_panel_api.py — get_snapshot() delegates to client.get_snapshot() + ✅ span_panel.py — update() calls get_snapshot(); populates all domain objects + ✅ span_panel_circuit.py — from_snapshot(SpanCircuitSnapshot) factory + ✅ span_panel_data.py — from_snapshot(SpanPanelSnapshot) factory + ✅ span_panel_hardware_status.py — from_snapshot(SpanPanelSnapshot) factory + ✅ span_panel_storage_battery.py — from_snapshot(SpanPanelSnapshot) factory Phase 2b pending (Gen3 hardware required, depends on 2a): ⏳ span_panel_api.py — _create_client() Gen3 branch; widen _client type ⏳ coordinator.py — SpanPanelPushCoordinator @@ -400,9 +403,12 @@ branch can be merged, since the integration depends on the new library API. | `custom_components/span_panel/config_flow.py` | Generation dropdown; `async_step_gen3_setup`; store generation in entry data | ✅ Done | | `custom_components/span_panel/sensors/factory.py` | Capability-gated sensor groups (DSM, energy, hardware, battery, solar) | ✅ Done | | `custom_components/span_panel/manifest.json` | Version 1.3.2; `span-panel-api[grpc]~=1.1.15` | ✅ Done | -| `custom_components/span_panel/span_panel_api.py` | `update()` calls `get_snapshot()`; widen `_client` to protocol type | ⏳ Phase 2a/2b | -| `custom_components/span_panel/span_panel.py` | Populate from `SpanPanelSnapshot` instead of OpenAPI types | ⏳ Phase 2a | -| `custom_components/span_panel/span_panel_circuit.py` | Wrap `SpanCircuitSnapshot` instead of OpenAPI `Circuit` | ⏳ Phase 2a | +| `custom_components/span_panel/span_panel_api.py` | Added `get_snapshot()` (Phase 2a); widen `_client` to protocol type (Phase 2b) | ✅ Phase 2a done / ⏳ Phase 2b | +| `custom_components/span_panel/span_panel.py` | `update()` calls `get_snapshot()`; all domain objects from snapshot | ✅ Done | +| `custom_components/span_panel/span_panel_circuit.py` | Added `from_snapshot(SpanCircuitSnapshot)` factory | ✅ Done | +| `custom_components/span_panel/span_panel_data.py` | Added `from_snapshot(SpanPanelSnapshot)` factory | ✅ Done | +| `custom_components/span_panel/span_panel_hardware_status.py` | Added `from_snapshot(SpanPanelSnapshot)` factory | ✅ Done | +| `custom_components/span_panel/span_panel_storage_battery.py` | Added `from_snapshot(SpanPanelSnapshot)` factory | ✅ Done | | `custom_components/span_panel/coordinator.py` | Add `SpanPanelPushCoordinator`; coordinator selection in setup | ⏳ Phase 2b | | `custom_components/span_panel/__init__.py` | Coordinator selection by `PUSH_STREAMING` capability | ⏳ Phase 2b | | `custom_components/span_panel/sensors/factory.py` | Gen3-only sensor entities (voltage, current, apparent/reactive power, frequency, power factor) | ⏳ Phase 2b | From d481c088730f22c78b70fd1933bd184d84c1e256 Mon Sep 17 00:00:00 2001 From: cayossarian Date: Tue, 17 Feb 2026 15:35:28 -0800 Subject: [PATCH 05/13] Phase 2b: Gen3 gRPC push-streaming integration and Gen3-only sensors coordinator.py: - Detect PUSH_STREAMING capability at init; pass update_interval=None to disable polling timer for Gen3 - _register_push_callback(), _on_push_data(), _async_push_update() drive entity updates from gRPC stream callbacks - _push_update_pending guard prevents stacking concurrent async tasks - async_shutdown() unregisters push callback before super() span_panel_api.py: - _create_client() Gen3 branch instantiates SpanGrpcClient - register_push_callback() exposes callback registration without callers accessing _client directly - Simulation always uses Gen2 transport: _panel_generation normalised to "gen2" when simulation_mode=True, regardless of panel_generation setting __init__.py: - async_unload_entry: fixed cleanup to use coordinator.span_panel (not the nonexistent coordinator.span_panel_api) - v1->v2 migration stamps panel_generation="gen2" on existing entries (all v1 entries pre-date Gen3 support) sensor_definitions.py: - CIRCUIT_GEN3_SENSORS: voltage, current, apparent/reactive power, frequency, power factor per circuit (gated on PUSH_STREAMING) - PANEL_GEN3_SENSORS: main feed voltage, current, frequency sensors/factory.py + sensors/circuit.py: - Gen3-only sensor creation from SpanCircuitSnapshot non-None fields span_panel.py, span_panel_circuit.py, span_panel_data.py: - snapshot-based update() and from_snapshot() factories docs/dev/gen3-grpc-integration-plan.md: - Mark Phase 2b complete; document circuit IID mapping bug fix in library --- custom_components/span_panel/__init__.py | 28 +- custom_components/span_panel/coordinator.py | 120 ++++++-- .../span_panel/sensor_definitions.py | 127 +++++++- .../span_panel/sensors/circuit.py | 100 +++++++ .../span_panel/sensors/factory.py | 34 ++- custom_components/span_panel/span_panel.py | 2 + .../span_panel/span_panel_api.py | 121 ++++++-- .../span_panel/span_panel_circuit.py | 13 + .../span_panel/span_panel_data.py | 22 ++ docs/dev/gen3-grpc-integration-plan.md | 274 +++++++++--------- 10 files changed, 638 insertions(+), 203 deletions(-) diff --git a/custom_components/span_panel/__init__.py b/custom_components/span_panel/__init__.py index 8c6b926..48f74fc 100644 --- a/custom_components/span_panel/__init__.py +++ b/custom_components/span_panel/__init__.py @@ -22,6 +22,7 @@ # Import config flow to ensure it's registered from . import config_flow # noqa: F401 # type: ignore[misc] from .const import ( + CONF_PANEL_GENERATION, CONF_SIMULATION_CONFIG, CONF_SIMULATION_OFFLINE_MINUTES, CONF_SIMULATION_START_TIME, @@ -103,10 +104,19 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> ) return False else: - # Normal successful migration + # Normal successful migration. + # Stamp CONF_PANEL_GENERATION on entries that pre-date Gen3 support — + # all v1 config entries were created against Gen2 hardware. + migrated_data = dict(config_entry.data) + if CONF_PANEL_GENERATION not in migrated_data: + migrated_data[CONF_PANEL_GENERATION] = "gen2" + _LOGGER.debug( + "Migration: set panel_generation=gen2 for entry %s", + config_entry.entry_id, + ) hass.config_entries.async_update_entry( config_entry, - data=config_entry.data, + data=migrated_data, options=config_entry.options, title=config_entry.title, version=CURRENT_CONFIG_VERSION, @@ -218,6 +228,7 @@ async def ha_compatible_delay(seconds: float) -> None: simulation_config_path=simulation_config_path, simulation_start_time=simulation_start_time, simulation_offline_minutes=simulation_offline_minutes, + panel_generation=config.get(CONF_PANEL_GENERATION, "auto"), ) _LOGGER.debug("Created SpanPanel instance: %s", span_panel) @@ -372,16 +383,13 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: coordinator_data = hass.data[DOMAIN].get(entry.entry_id) if coordinator_data and COORDINATOR in coordinator_data: coordinator = coordinator_data[COORDINATOR] - # Clean up the API client resources - if hasattr(coordinator, "span_panel_api") and coordinator.span_panel_api: - span_panel = coordinator.span_panel_api + # Close the SpanPanel (stops gRPC streaming, closes HTTP connections, etc.) + span_panel = getattr(coordinator, "span_panel", None) + if isinstance(span_panel, SpanPanel): try: - # SpanPanel has a close method that properly cleans up the API client - if isinstance(span_panel, SpanPanel): - await span_panel.close() - _LOGGER.debug("Successfully closed SpanPanel API client") + await span_panel.close() + _LOGGER.debug("Successfully closed SpanPanel API client") except TypeError as e: - # Handle non-awaitable objects gracefully _LOGGER.debug("API close method is not awaitable, skipping cleanup: %s", e) except Exception as e: _LOGGER.error("Error during API cleanup: %s", e) diff --git a/custom_components/span_panel/coordinator.py b/custom_components/span_panel/coordinator.py index bbcbaa4..dbedcdd 100644 --- a/custom_components/span_panel/coordinator.py +++ b/custom_components/span_panel/coordinator.py @@ -2,6 +2,7 @@ from __future__ import annotations +from collections.abc import Callable from datetime import timedelta import logging from time import time as _epoch_time @@ -15,6 +16,7 @@ HomeAssistantError, ) from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from span_panel_api import PanelCapability from span_panel_api.exceptions import ( SpanPanelAPIError, SpanPanelConnectionError, @@ -60,50 +62,65 @@ def __init__( self._panel_offline = False # Track last grace period value for comparison self._last_grace_period = config_entry.options.get(ENERGY_REPORTING_GRACE_PERIOD, 15) + # Push-streaming support (Gen3 gRPC panels) + self._push_unregister: Callable[[], None] | None = None + self._push_update_pending: bool = False - # Get scan interval from options, with fallback to default - raw_scan_interval = config_entry.options.get( - CONF_SCAN_INTERVAL, int(DEFAULT_SCAN_INTERVAL.total_seconds()) - ) + # Detect Gen3 push-streaming panels before selecting update interval. + is_push_streaming = PanelCapability.PUSH_STREAMING in span_panel.api.capabilities - # Coerce scan interval to integer seconds, clamp to minimum of 5 - try: - # Accept strings, floats, and ints; e.g., "15", 15.0, 15 - scan_interval_seconds = int(float(raw_scan_interval)) - except (TypeError, ValueError): - scan_interval_seconds = int(DEFAULT_SCAN_INTERVAL.total_seconds()) - - if scan_interval_seconds < 5: - _LOGGER.debug( - "Configured scan interval %s is below minimum; clamping to 5 seconds", - scan_interval_seconds, + if is_push_streaming: + # Gen3: disable polling timer; updates are driven by gRPC push callbacks. + update_interval: timedelta | None = None + _LOGGER.info( + "Span Panel coordinator: Gen3 push-streaming mode — polling timer disabled" ) - scan_interval_seconds = 5 + else: + # Gen2: compute polling interval from config options. + raw_scan_interval = config_entry.options.get( + CONF_SCAN_INTERVAL, int(DEFAULT_SCAN_INTERVAL.total_seconds()) + ) + + try: + scan_interval_seconds = int(float(raw_scan_interval)) + except (TypeError, ValueError): + scan_interval_seconds = int(DEFAULT_SCAN_INTERVAL.total_seconds()) - if str(raw_scan_interval) != str(scan_interval_seconds): - _LOGGER.debug( - "Coerced scan interval option from raw=%s to %s seconds", - raw_scan_interval, + if scan_interval_seconds < 5: + _LOGGER.debug( + "Configured scan interval %s is below minimum; clamping to 5 seconds", + scan_interval_seconds, + ) + scan_interval_seconds = 5 + + if str(raw_scan_interval) != str(scan_interval_seconds): + _LOGGER.debug( + "Coerced scan interval option from raw=%s to %s seconds", + raw_scan_interval, + scan_interval_seconds, + ) + + _LOGGER.info( + "Span Panel coordinator: update interval set to %s seconds", scan_interval_seconds, ) - - # Log at INFO so it is visible without debug logging - _LOGGER.info( - "Span Panel coordinator: update interval set to %s seconds", - scan_interval_seconds, - ) + update_interval = timedelta(seconds=scan_interval_seconds) super().__init__( hass, _LOGGER, config_entry=config_entry, name=DOMAIN, - update_interval=timedelta(seconds=scan_interval_seconds), + update_interval=update_interval, ) # Ensure config_entry is properly set after super().__init__ self.config_entry = config_entry + # Register push callback now that the event loop (hass) is available. + if is_push_streaming: + self._register_push_callback() + @property def panel_offline(self) -> bool: """Return True if the panel is currently offline/unreachable.""" @@ -117,6 +134,55 @@ def request_reload(self) -> None: """Request a reload of the integration.""" self._reload_requested = True + # ------------------------------------------------------------------ + # Gen3 push-streaming support + # ------------------------------------------------------------------ + + def _register_push_callback(self) -> None: + """Register a callback with the Gen3 gRPC streaming client. + + The callback fires each time the gRPC stream pushes new panel data. + It schedules an async task on the HA event loop to update coordinator + data without blocking the streaming loop. + """ + unregister = self.span_panel.api.register_push_callback(self._on_push_data) + if unregister is None: + _LOGGER.warning("Gen3 push streaming requested but client does not support callbacks") + return + self._push_unregister = unregister + _LOGGER.debug("Registered Gen3 push-streaming coordinator callback") + + def _on_push_data(self) -> None: + """Sync callback invoked by the gRPC stream on each new notification. + + Guards against concurrent update tasks: if one is already pending + we skip scheduling another — the in-flight task will read the latest + data when it runs. + """ + if self._push_update_pending: + return + self._push_update_pending = True + self.hass.async_create_task(self._async_push_update()) + + async def _async_push_update(self) -> None: + """Apply incoming push data to coordinator state and notify listeners.""" + try: + await self.span_panel.update() + self._panel_offline = False + self.async_set_updated_data(self.span_panel) + except Exception as err: # pylint: disable=broad-exception-caught + self._panel_offline = True + _LOGGER.warning("Gen3 push update failed: %s", err) + finally: + self._push_update_pending = False + + async def async_shutdown(self) -> None: + """Shutdown coordinator and unregister Gen3 push-streaming callback.""" + if self._push_unregister is not None: + self._push_unregister() + self._push_unregister = None + await super().async_shutdown() + async def _async_update_data(self) -> SpanPanel: """Fetch data from API endpoint.""" try: diff --git a/custom_components/span_panel/sensor_definitions.py b/custom_components/span_panel/sensor_definitions.py index 87d3a3f..d465184 100644 --- a/custom_components/span_panel/sensor_definitions.py +++ b/custom_components/span_panel/sensor_definitions.py @@ -19,7 +19,14 @@ SensorEntityDescription, SensorStateClass, ) -from homeassistant.const import PERCENTAGE, UnitOfEnergy, UnitOfPower +from homeassistant.const import ( + PERCENTAGE, + UnitOfElectricCurrent, + UnitOfElectricPotential, + UnitOfEnergy, + UnitOfFrequency, + UnitOfPower, +) from .const import ( CIRCUITS_ENERGY_CONSUMED, @@ -36,7 +43,7 @@ class SpanPanelCircuitsRequiredKeysMixin: """Required keys mixin for Span Panel circuit sensors.""" - value_fn: Callable[[SpanPanelCircuit], float] + value_fn: Callable[[SpanPanelCircuit], float | None] @dataclass(frozen=True) @@ -50,7 +57,7 @@ class SpanPanelCircuitsSensorEntityDescription( class SpanPanelDataRequiredKeysMixin: """Required keys mixin for Span Panel data sensors.""" - value_fn: Callable[[SpanPanelData], float | str] + value_fn: Callable[[SpanPanelData], float | str | None] @dataclass(frozen=True) @@ -383,3 +390,117 @@ class SpanSolarSensorEntityDescription(SensorEntityDescription): calculation_type="sum", ), ) + +# Gen3-only circuit sensor definitions (gated on PanelCapability.PUSH_STREAMING) +CIRCUIT_GEN3_SENSORS: tuple[ + SpanPanelCircuitsSensorEntityDescription, + SpanPanelCircuitsSensorEntityDescription, + SpanPanelCircuitsSensorEntityDescription, + SpanPanelCircuitsSensorEntityDescription, + SpanPanelCircuitsSensorEntityDescription, + SpanPanelCircuitsSensorEntityDescription, +] = ( + SpanPanelCircuitsSensorEntityDescription( + key="circuit_voltage_v", + name="Voltage", + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=1, + device_class=SensorDeviceClass.VOLTAGE, + value_fn=lambda circuit: circuit.voltage_v, + entity_registry_enabled_default=True, + entity_registry_visible_default=True, + ), + SpanPanelCircuitsSensorEntityDescription( + key="circuit_current_a", + name="Current", + native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=2, + device_class=SensorDeviceClass.CURRENT, + value_fn=lambda circuit: circuit.current_a, + entity_registry_enabled_default=True, + entity_registry_visible_default=True, + ), + SpanPanelCircuitsSensorEntityDescription( + key="circuit_apparent_power_va", + name="Apparent Power", + native_unit_of_measurement="VA", + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=0, + device_class=SensorDeviceClass.APPARENT_POWER, + value_fn=lambda circuit: circuit.apparent_power_va, + entity_registry_enabled_default=True, + entity_registry_visible_default=True, + ), + SpanPanelCircuitsSensorEntityDescription( + key="circuit_reactive_power_var", + name="Reactive Power", + native_unit_of_measurement="var", + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=0, + device_class=SensorDeviceClass.REACTIVE_POWER, + value_fn=lambda circuit: circuit.reactive_power_var, + entity_registry_enabled_default=True, + entity_registry_visible_default=True, + ), + SpanPanelCircuitsSensorEntityDescription( + key="circuit_frequency_hz", + name="Frequency", + native_unit_of_measurement=UnitOfFrequency.HERTZ, + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=2, + device_class=SensorDeviceClass.FREQUENCY, + value_fn=lambda circuit: circuit.frequency_hz, + entity_registry_enabled_default=True, + entity_registry_visible_default=True, + ), + SpanPanelCircuitsSensorEntityDescription( + key="circuit_power_factor", + name="Power Factor", + native_unit_of_measurement=PERCENTAGE, + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=1, + device_class=SensorDeviceClass.POWER_FACTOR, + value_fn=lambda circuit: (circuit.power_factor * 100) + if circuit.power_factor is not None + else None, + entity_registry_enabled_default=True, + entity_registry_visible_default=True, + ), +) + +# Gen3-only panel-level sensor definitions (gated on PanelCapability.PUSH_STREAMING) +PANEL_GEN3_SENSORS: tuple[ + SpanPanelDataSensorEntityDescription, + SpanPanelDataSensorEntityDescription, + SpanPanelDataSensorEntityDescription, +] = ( + SpanPanelDataSensorEntityDescription( + key="mainVoltageV", + name="Main Voltage", + native_unit_of_measurement=UnitOfElectricPotential.VOLT, + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=1, + device_class=SensorDeviceClass.VOLTAGE, + value_fn=lambda panel_data: panel_data.main_voltage_v, + ), + SpanPanelDataSensorEntityDescription( + key="mainCurrentA", + name="Main Current", + native_unit_of_measurement=UnitOfElectricCurrent.AMPERE, + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=2, + device_class=SensorDeviceClass.CURRENT, + value_fn=lambda panel_data: panel_data.main_current_a, + ), + SpanPanelDataSensorEntityDescription( + key="mainFrequencyHz", + name="Main Frequency", + native_unit_of_measurement=UnitOfFrequency.HERTZ, + state_class=SensorStateClass.MEASUREMENT, + suggested_display_precision=2, + device_class=SensorDeviceClass.FREQUENCY, + value_fn=lambda panel_data: panel_data.main_frequency_hz, + ), +) diff --git a/custom_components/span_panel/sensors/circuit.py b/custom_components/span_panel/sensors/circuit.py index bb1c3dc..a9caee6 100644 --- a/custom_components/span_panel/sensors/circuit.py +++ b/custom_components/span_panel/sensors/circuit.py @@ -286,6 +286,106 @@ def extra_state_attributes(self) -> dict[str, Any] | None: return attributes if attributes else None +class SpanCircuitGen3Sensor( + SpanSensorBase[SpanPanelCircuitsSensorEntityDescription, SpanPanelCircuit] +): + """Gen3-only circuit sensor for push-streamed electrical metrics. + + Used for voltage, current, apparent/reactive power, frequency, and power + factor — all of which are only available from Gen3 (gRPC) panels. + """ + + _API_KEY_MAP: dict[str, str] = { + "circuit_voltage_v": "voltageV", + "circuit_current_a": "currentA", + "circuit_apparent_power_va": "apparentPowerVa", + "circuit_reactive_power_var": "reactivePowerVar", + "circuit_frequency_hz": "frequencyHz", + "circuit_power_factor": "powerFactor", + } + + def __init__( + self, + data_coordinator: SpanPanelCoordinator, + description: SpanPanelCircuitsSensorEntityDescription, + span_panel: SpanPanel, + circuit_id: str, + ) -> None: + """Initialize the Gen3 circuit metric sensor.""" + self.circuit_id = circuit_id + self.original_key = description.key + + description_with_circuit = SpanPanelCircuitsSensorEntityDescription( + key=circuit_id, + name=description.name, + native_unit_of_measurement=description.native_unit_of_measurement, + state_class=description.state_class, + suggested_display_precision=description.suggested_display_precision, + device_class=description.device_class, + value_fn=description.value_fn, + entity_registry_enabled_default=description.entity_registry_enabled_default, + entity_registry_visible_default=description.entity_registry_visible_default, + ) + + super().__init__(data_coordinator, description_with_circuit, span_panel) + + def _generate_unique_id( + self, span_panel: SpanPanel, description: SpanPanelCircuitsSensorEntityDescription + ) -> str: + """Generate unique ID for Gen3 circuit metric sensors.""" + api_key = self._API_KEY_MAP.get(self.original_key, self.original_key) + return construct_circuit_unique_id_for_entry( + self.coordinator, span_panel, self.circuit_id, api_key, self._device_name + ) + + def _generate_friendly_name( + self, span_panel: SpanPanel, description: SpanPanelCircuitsSensorEntityDescription + ) -> str | None: + """Generate friendly name for Gen3 circuit metric sensors.""" + circuit = span_panel.circuits.get(self.circuit_id) + if not circuit: + return construct_unmapped_friendly_name( + self.circuit_id, str(description.name or "Sensor") + ) + + use_circuit_numbers = self.coordinator.config_entry.options.get(USE_CIRCUIT_NUMBERS, False) + + if use_circuit_numbers: + if circuit.tabs and len(circuit.tabs) == 2: + sorted_tabs = sorted(circuit.tabs) + circuit_identifier = f"Circuit {sorted_tabs[0]} {sorted_tabs[1]}" + elif circuit.tabs and len(circuit.tabs) == 1: + circuit_identifier = f"Circuit {circuit.tabs[0]}" + else: + circuit_identifier = f"Circuit {self.circuit_id}" + else: + if circuit.name is None: + return None + circuit_identifier = circuit.name + + return f"{circuit_identifier} {description.name or 'Sensor'}" + + def _generate_panel_name( + self, span_panel: SpanPanel, description: SpanPanelCircuitsSensorEntityDescription + ) -> str | None: + """Generate panel name for Gen3 circuit metric sensors.""" + circuit = span_panel.circuits.get(self.circuit_id) + if not circuit: + return construct_unmapped_friendly_name( + self.circuit_id, str(description.name or "Sensor") + ) + if circuit.name is None: + return None + return f"{circuit.name} {description.name or 'Sensor'}" + + def get_data_source(self, span_panel: SpanPanel) -> SpanPanelCircuit: + """Get the data source for the Gen3 circuit metric sensor.""" + circuit = span_panel.circuits.get(self.circuit_id) + if circuit is None: + raise ValueError(f"Circuit {self.circuit_id} not found in panel data") + return circuit + + class SpanUnmappedCircuitSensor( SpanSensorBase[SpanPanelCircuitsSensorEntityDescription, SpanPanelCircuit] ): diff --git a/custom_components/span_panel/sensors/factory.py b/custom_components/span_panel/sensors/factory.py index 3b29f8d..9e399b0 100644 --- a/custom_components/span_panel/sensors/factory.py +++ b/custom_components/span_panel/sensors/factory.py @@ -24,9 +24,11 @@ ) from custom_components.span_panel.sensor_definitions import ( BATTERY_SENSOR, + CIRCUIT_GEN3_SENSORS, CIRCUIT_SENSORS, PANEL_DATA_STATUS_SENSORS, PANEL_ENERGY_SENSORS, + PANEL_GEN3_SENSORS, PANEL_POWER_SENSORS, SOLAR_SENSORS, STATUS_SENSORS, @@ -34,7 +36,12 @@ ) from custom_components.span_panel.span_panel import SpanPanel -from .circuit import SpanCircuitEnergySensor, SpanCircuitPowerSensor, SpanUnmappedCircuitSensor +from .circuit import ( + SpanCircuitEnergySensor, + SpanCircuitGen3Sensor, + SpanCircuitPowerSensor, + SpanUnmappedCircuitSensor, +) from .panel import ( SpanPanelBattery, SpanPanelEnergySensor, @@ -81,6 +88,11 @@ def create_panel_sensors( for description_ss in STATUS_SENSORS: entities.append(SpanPanelStatus(coordinator, description_ss, span_panel)) + # Add Gen3 main-feed metrics (voltage, current, frequency) — Gen3-only. + if PanelCapability.PUSH_STREAMING in capabilities: + for description in PANEL_GEN3_SENSORS: + entities.append(SpanPanelPanelStatus(coordinator, description, span_panel)) + return entities @@ -149,6 +161,23 @@ def create_unmapped_circuit_sensors( return entities +def create_gen3_circuit_sensors( + coordinator: SpanPanelCoordinator, span_panel: SpanPanel +) -> list[SpanCircuitGen3Sensor]: + """Create Gen3-only per-circuit metric sensors gated on PUSH_STREAMING capability.""" + entities: list[SpanCircuitGen3Sensor] = [] + + if PanelCapability.PUSH_STREAMING not in span_panel.api.capabilities: + return entities + + named_circuits = [cid for cid in span_panel.circuits if not cid.startswith("unmapped_tab_")] + for circuit_id in named_circuits: + for description in CIRCUIT_GEN3_SENSORS: + entities.append(SpanCircuitGen3Sensor(coordinator, description, span_panel, circuit_id)) + + return entities + + def create_battery_sensors( coordinator: SpanPanelCoordinator, span_panel: SpanPanel, config_entry: ConfigEntry ) -> list[SpanPanelBattery]: @@ -250,6 +279,7 @@ def create_native_sensors( | SpanPanelEnergySensor | SpanCircuitPowerSensor | SpanCircuitEnergySensor + | SpanCircuitGen3Sensor | SpanUnmappedCircuitSensor | SpanPanelBattery | SpanSolarSensor @@ -263,6 +293,7 @@ def create_native_sensors( | SpanPanelEnergySensor | SpanCircuitPowerSensor | SpanCircuitEnergySensor + | SpanCircuitGen3Sensor | SpanUnmappedCircuitSensor | SpanPanelBattery | SpanSolarSensor @@ -272,6 +303,7 @@ def create_native_sensors( # Create different sensor types entities.extend(create_panel_sensors(coordinator, span_panel, config_entry)) entities.extend(create_circuit_sensors(coordinator, span_panel, config_entry)) + entities.extend(create_gen3_circuit_sensors(coordinator, span_panel)) entities.extend(create_unmapped_circuit_sensors(coordinator, span_panel)) entities.extend(create_battery_sensors(coordinator, span_panel, config_entry)) entities.extend(create_solar_sensors(coordinator, span_panel, config_entry)) diff --git a/custom_components/span_panel/span_panel.py b/custom_components/span_panel/span_panel.py index 3be790b..a165984 100644 --- a/custom_components/span_panel/span_panel.py +++ b/custom_components/span_panel/span_panel.py @@ -46,6 +46,7 @@ def __init__( simulation_config_path: str | None = None, simulation_start_time: datetime | None = None, simulation_offline_minutes: int = 0, + panel_generation: str = "auto", ) -> None: """Initialize the Span Panel.""" self._options = options @@ -59,6 +60,7 @@ def __init__( simulation_config_path, simulation_start_time, simulation_offline_minutes, + panel_generation, ) self._status: SpanPanelHardwareStatus | None = None self._panel: SpanPanelData | None = None diff --git a/custom_components/span_panel/span_panel_api.py b/custom_components/span_panel/span_panel_api.py index bdd4bb5..d1f773f 100644 --- a/custom_components/span_panel/span_panel_api.py +++ b/custom_components/span_panel/span_panel_api.py @@ -1,13 +1,23 @@ """Span Panel API - Updated to use span-panel-api package.""" +from __future__ import annotations + +from collections.abc import Callable from copy import deepcopy from datetime import datetime import logging import os -from typing import Any +from typing import Any, cast import uuid -from span_panel_api import PanelCapability, SpanPanelClient, SpanPanelSnapshot, set_async_delay_func +from span_panel_api import ( + PanelCapability, + SpanPanelClient, + SpanPanelClientProtocol, + SpanPanelSnapshot, + StreamingCapableProtocol, + set_async_delay_func, +) from span_panel_api.exceptions import ( SpanPanelAPIError, SpanPanelAuthError, @@ -50,6 +60,7 @@ def __init__( simulation_config_path: str | None = None, simulation_start_time: datetime | None = None, simulation_offline_minutes: int = 0, + panel_generation: str = "auto", ) -> None: """Initialize the Span Panel API.""" # For simulation mode, keep the original host (which should be the serial number) @@ -57,6 +68,8 @@ def __init__( self.host: str = host if simulation_mode else host.lower() self.access_token: str | None = access_token self.options: Options | None = options + # Simulation only runs on the Gen2 REST transport; override any gen3 selection. + self._panel_generation: str = "gen2" if simulation_mode else panel_generation self.scan_interval: int = scan_interval or 15 # Default to 15 seconds self.use_ssl: bool = use_ssl self.simulation_mode: bool = simulation_mode @@ -85,7 +98,7 @@ def __init__( self._retry_backoff_multiplier = DEFAULT_API_RETRY_BACKOFF_MULTIPLIER # Initialize client as None - will be created in setup() - self._client: SpanPanelClient | None = None + self._client: SpanPanelClientProtocol | None = None def _is_panel_offline(self) -> bool: """Check if the panel should be offline based on simulation settings. @@ -153,8 +166,33 @@ def set_simulation_offline_mode(self, offline_minutes: int) -> None: self.offline_start_time = None _LOGGER.info("[SpanPanelApi] Disabled simulation offline mode") + @property + def _gen2_client(self) -> SpanPanelClient: + """Return the Gen2 REST client, raising if the active transport is not Gen2. + + The _panel_generation guard ensures _client is always SpanPanelClient when + not "gen3". cast() is used instead of isinstance so that test environments + that mock SpanPanelClient as a MagicMock (non-type) work correctly. + """ + if self._panel_generation == "gen3": + raise SpanPanelAPIError( + "Operation not supported: panel is not connected via Gen2 (REST) transport" + ) + if self._client is None: + raise SpanPanelAPIError("API client has been closed") + return cast(SpanPanelClient, self._client) + def _create_client(self) -> None: - """Create the SpanPanelClient with stored parameters.""" + """Create the appropriate transport client based on panel generation.""" + if self._panel_generation == "gen3": + _LOGGER.debug("[SpanPanelApi] Creating SpanGrpcClient for host=%s", self.host) + from span_panel_api.grpc import ( # pylint: disable=import-outside-toplevel + SpanGrpcClient, # noqa: PLC0415 # type: ignore[import-untyped] + ) + + self._client = SpanGrpcClient(host=self.host) + return + _LOGGER.debug("[SpanPanelApi] Creating SpanPanelClient for host=%s", self.host) # Determine simulation config path if in simulation mode @@ -257,6 +295,22 @@ async def setup(self) -> None: self._create_client() self._client_created = True + if self._client is None: + raise SpanPanelAPIError("Failed to create API client") + + if self._panel_generation == "gen3": + _LOGGER.debug("[SpanPanelApi] Gen3 setup: connecting via gRPC") + try: + if not await self._client.connect(): + raise SpanPanelConnectionError("Failed to connect to Gen3 SPAN panel via gRPC") + await cast(StreamingCapableProtocol, self._client).start_streaming() + _LOGGER.debug("[SpanPanelApi] Gen3 streaming started") + except Exception as e: + _LOGGER.error("[SpanPanelApi] Gen3 setup failed: %s", e) + await self.close() + raise + return + try: # If we have a token, verify it works if self.access_token: @@ -278,7 +332,12 @@ async def setup(self) -> None: raise async def _ensure_authenticated(self) -> None: - """Ensure we have valid authentication, re-authenticate if needed.""" + """Ensure we have valid authentication, re-authenticate if needed. + + Gen3 panels do not use JWT authentication; this method is a no-op for them. + """ + if self._panel_generation == "gen3": + return if not self._authenticated: _LOGGER.debug("[SpanPanelApi] Re-authentication needed") if self._client is None: @@ -286,7 +345,7 @@ async def _ensure_authenticated(self) -> None: try: # Generate a unique client name for re-authentication client_name = f"home-assistant-{uuid.uuid4()}" - auth_response = await self._client.authenticate( + auth_response = await self._gen2_client.authenticate( client_name, "Home Assistant Local Span Integration" ) self.access_token = auth_response.access_token @@ -305,7 +364,14 @@ async def ping(self) -> bool: self._ensure_client_open() if self._client is None: return False - # status endpoint doesn't require auth. + + if self._panel_generation == "gen3": + try: + return bool(await self._client.ping()) + except Exception: + return False + + # Gen2: status endpoint doesn't require auth. try: await self.get_status_data() return True @@ -321,6 +387,14 @@ async def ping_with_auth(self) -> bool: self._ensure_client_open() if self._client is None: return False + + if self._panel_generation == "gen3": + # Gen3 has no authentication; a successful ping implies a working connection. + try: + return bool(await self._client.ping()) + except Exception: + return False + try: # Use get_panel_data() since it requires authentication await self.get_panel_data() @@ -341,12 +415,10 @@ async def ping_with_auth(self) -> bool: async def get_access_token(self) -> str: """Get the access token.""" self._ensure_client_open() - if self._client is None: - raise SpanPanelAPIError("API client has been closed") try: # Generate a unique client name client_name = f"home-assistant-{uuid.uuid4()}" - auth_response = await self._client.authenticate( + auth_response = await self._gen2_client.authenticate( client_name, "Home Assistant Local Span Integration" ) @@ -368,7 +440,7 @@ async def get_status_data(self) -> SpanPanelHardwareStatus: raise SpanPanelAPIError("API client has been closed") self._debug_check_client("get_status_data") try: - status_response = await self._client.get_status() + status_response = await self._gen2_client.get_status() # Convert the attrs model to dict and then to our data class status_dict = status_response.to_dict() @@ -401,7 +473,7 @@ async def get_panel_data(self) -> SpanPanelData: self._debug_check_client("get_panel_data") try: await self._ensure_authenticated() - panel_response = await self._client.get_panel_state() + panel_response = await self._gen2_client.get_panel_state() # Convert the attrs model to dict and deep copy before processing raw_data: Any = deepcopy(panel_response.to_dict()) @@ -445,7 +517,7 @@ async def get_circuits_data(self) -> dict[str, SpanPanelCircuit]: self._debug_check_client("get_circuits_data") try: await self._ensure_authenticated() - circuits_response = await self._client.get_circuits() + circuits_response = await self._gen2_client.get_circuits() # Extract circuits from the response raw_circuits_data = circuits_response.circuits.additional_properties @@ -497,7 +569,7 @@ async def get_storage_battery_data(self) -> SpanPanelStorageBattery: self._debug_check_client("get_storage_battery_data") try: await self._ensure_authenticated() - storage_response = await self._client.get_storage_soe() + storage_response = await self._gen2_client.get_storage_soe() # Extract SOE data from the response storage_battery_data = storage_response.soe.to_dict() @@ -571,7 +643,7 @@ async def set_relay(self, circuit: SpanPanelCircuit, state: CircuitRelayState) - self._debug_check_client("set_relay") try: await self._ensure_authenticated() - await self._client.set_circuit_relay(circuit.circuit_id, state.name) + await self._gen2_client.set_circuit_relay(circuit.circuit_id, state.name) except SpanPanelRetriableError as e: _LOGGER.warning("Retriable error setting relay state (will retry): %s", e) @@ -600,7 +672,7 @@ async def set_priority(self, circuit: SpanPanelCircuit, priority: CircuitPriorit self._debug_check_client("set_priority") try: await self._ensure_authenticated() - await self._client.set_circuit_priority(circuit.circuit_id, priority.name) + await self._gen2_client.set_circuit_priority(circuit.circuit_id, priority.name) except SpanPanelRetriableError as e: _LOGGER.warning("Retriable error setting priority (will retry): %s", e) @@ -641,7 +713,7 @@ async def get_all_data(self, include_battery: bool = False) -> dict[str, Any]: try: # Use the client's parallel batch method - raw_data = await self._client.get_all_data(include_battery=include_battery) + raw_data = await self._gen2_client.get_all_data(include_battery=include_battery) # Process data using the same logic as individual methods result: dict[str, Any] = {} @@ -702,11 +774,26 @@ def capabilities(self) -> PanelCapability: return self._client.capabilities return PanelCapability.GEN2_FULL + def register_push_callback(self, cb: Callable[[], None]) -> Callable[[], None] | None: + """Register a callback for Gen3 push-streaming updates. + + Returns the unregister callable, or None when the active transport is + not push-streaming capable (e.g. Gen2 REST panels). + """ + if self._client is None or not isinstance(self._client, StreamingCapableProtocol): + return None + result: Callable[[], None] = cast(StreamingCapableProtocol, self._client).register_callback( + cb + ) + return result + async def close(self) -> None: """Close the API client and clean up resources.""" _LOGGER.debug("[SpanPanelApi] Closing API client for host=%s", self.host) if self._client is not None: try: + if self._panel_generation == "gen3": + await cast(StreamingCapableProtocol, self._client).stop_streaming() await self._client.close() except Exception as e: _LOGGER.warning("Error closing API client: %s", e) diff --git a/custom_components/span_panel/span_panel_circuit.py b/custom_components/span_panel/span_panel_circuit.py index 5ba2144..361d6f1 100644 --- a/custom_components/span_panel/span_panel_circuit.py +++ b/custom_components/span_panel/span_panel_circuit.py @@ -26,6 +26,13 @@ class SpanPanelCircuit: is_user_controllable: bool is_sheddable: bool is_never_backup: bool + # Gen3-only fields (None for Gen2 panels — entities gated on PUSH_STREAMING capability) + voltage_v: float | None = None + current_a: float | None = None + apparent_power_va: float | None = None + reactive_power_var: float | None = None + frequency_hz: float | None = None + power_factor: float | None = None breaker_positions: list[int] = field(default_factory=list) metadata: dict[str, Any] = field(default_factory=dict) circuit_config: dict[str, Any] = field(default_factory=dict) @@ -96,6 +103,12 @@ def from_snapshot(snapshot: SpanCircuitSnapshot) -> "SpanPanelCircuit": is_user_controllable=relay is not None, is_sheddable=False, is_never_backup=False, + voltage_v=snapshot.voltage_v, + current_a=snapshot.current_a, + apparent_power_va=snapshot.apparent_power_va, + reactive_power_var=snapshot.reactive_power_var, + frequency_hz=snapshot.frequency_hz, + power_factor=snapshot.power_factor, ) def copy(self) -> "SpanPanelCircuit": diff --git a/custom_components/span_panel/span_panel_data.py b/custom_components/span_panel/span_panel_data.py index d2dd5af..691d479 100644 --- a/custom_components/span_panel/span_panel_data.py +++ b/custom_components/span_panel/span_panel_data.py @@ -32,6 +32,10 @@ class SpanPanelData: dsm_grid_state: str dsm_state: str current_run_config: str + # Gen3-only panel fields (None for Gen2 — entities gated on PUSH_STREAMING capability) + main_voltage_v: float | None = None + main_current_a: float | None = None + main_frequency_hz: float | None = None main_meter_energy: dict[str, Any] = field(default_factory=dict) feedthrough_energy: dict[str, Any] = field(default_factory=dict) solar_data: dict[str, Any] = field(default_factory=dict) @@ -88,6 +92,9 @@ def from_snapshot(cls, snapshot: SpanPanelSnapshot) -> "SpanPanelData": dsm_grid_state=snapshot.dsm_grid_state or "", dsm_state=snapshot.dsm_state or "", current_run_config=snapshot.current_run_config or "", + main_voltage_v=snapshot.main_voltage_v, + main_current_a=snapshot.main_current_a, + main_frequency_hz=snapshot.main_frequency_hz, ) def copy(self) -> "SpanPanelData": @@ -124,3 +131,18 @@ def feedthroughEnergyProducedWh(self) -> float: def feedthroughEnergyConsumedWh(self) -> float: """Feedthrough consumed energy in Wh (camelCase for sensor mapping).""" return self.feedthrough_energy_consumed + + @property + def mainVoltageV(self) -> float | None: + """Main feed voltage in V (camelCase for sensor mapping).""" + return self.main_voltage_v + + @property + def mainCurrentA(self) -> float | None: + """Main feed current in A (camelCase for sensor mapping).""" + return self.main_current_a + + @property + def mainFrequencyHz(self) -> float | None: + """Main feed frequency in Hz (camelCase for sensor mapping).""" + return self.main_frequency_hz diff --git a/docs/dev/gen3-grpc-integration-plan.md b/docs/dev/gen3-grpc-integration-plan.md index 25b3cf4..a571981 100644 --- a/docs/dev/gen3-grpc-integration-plan.md +++ b/docs/dev/gen3-grpc-integration-plan.md @@ -5,9 +5,7 @@ **Library work**: Complete on `span-panel-api` branch [`grpc_addition`](https://github.com/SpanPanel/span-panel-api/tree/grpc_addition) — version **1.1.15**. -**Integration work**: Phase 1 and Phase 2a complete on branch `gen3-grpc-integration` — version **1.3.2**. -Phase 2b (push coordinator, Gen3 power-metric sensors) is deferred until Gen3 hardware -is available for testing. +**Integration work**: Phases 1, 2a, and 2b complete on branch `gen3-grpc-integration` — version **1.3.2**. --- @@ -26,7 +24,7 @@ The library's `grpc_addition` branch introduces: - `SpanGrpcClient` — the Gen3 gRPC transport (migrated from PR #169's `gen3/`) - `create_span_client()` — factory with auto-detection -**Reference**: `span-panel-api/docs/Dev/grpc-transport-design.md` on the +**Reference**: `span-panel-api/docs/dev/grpc-transport-design.md` on the `grpc_addition` branch contains the full transport-layer architecture and interface specification. @@ -59,12 +57,6 @@ from span_panel_api import PanelCapability @property def capabilities(self) -> PanelCapability: - """Return the panel's capabilities. - - Reads directly from the underlying client so the value reflects the - connected transport (GEN2_FULL for OpenAPI/HTTP, GEN3_INITIAL for gRPC). - Falls back to GEN2_FULL when the client has not yet been created. - """ if self._client is not None: return self._client.capabilities return PanelCapability.GEN2_FULL @@ -88,7 +80,7 @@ _BASE_PLATFORMS: list[Platform] = [ ] _CAPABILITY_PLATFORMS: dict[PanelCapability, Platform] = { - PanelCapability.RELAY_CONTROL: Platform.SWITCH, + PanelCapability.RELAY_CONTROL: Platform.SWITCH, PanelCapability.PRIORITY_CONTROL: Platform.SELECT, } @@ -124,9 +116,8 @@ unload_ok = await hass.config_entries.async_unload_platforms(entry, active_platf **Gen2 result**: all four platforms loaded (`BINARY_SENSOR`, `SENSOR`, `SWITCH`, `SELECT`) — identical to previous behaviour. -**Gen3 result** (when `_create_client()` is updated in Phase 2): -`GEN3_INITIAL` = `PUSH_STREAMING` only → only `BINARY_SENSOR` + `SENSOR` -loaded; no switches, no selects. +**Gen3 result**: `GEN3_INITIAL` = `PUSH_STREAMING` only → only +`BINARY_SENSOR` + `SENSOR` loaded; no switches, no selects. ### 4. `config_flow.py` — Panel Generation Selector @@ -195,155 +186,140 @@ capability flag is absent. All gates read from `span_panel.api.capabilities`: ## Phase 2a — Snapshot Migration (Gen2 hardware, no Gen3 hardware required) -The current data path calls four individual API methods and maps OpenAPI-generated -types into integration domain objects. This phase migrates to `get_snapshot()` as -the single data-fetch call for both generations, eliminating any dependency on -OpenAPI types above `span-panel-api`. +The current data path called four individual API methods and mapped +OpenAPI-generated types into integration domain objects. This phase migrates +to `get_snapshot()` as the single data-fetch call for both generations, +eliminating any dependency on OpenAPI types above `span-panel-api`. -### Why This Must Precede Phase 2b +### `SpanPanel.update()` — single `get_snapshot()` call -Phase 2b slots in `SpanGrpcClient` behind the same interface. If entity classes -still read from OpenAPI-backed properties, Gen3 data has nowhere to go. Completing -this migration means entities read from `SpanCircuitSnapshot` fields — the same -fields `SpanGrpcClient.get_snapshot()` populates — so Gen3 entities require no -additional changes for the metrics that both generations share. +```python +snapshot = await self.api.get_snapshot() +self._update_status(SpanPanelHardwareStatus.from_snapshot(snapshot)) +self._update_panel(SpanPanelData.from_snapshot(snapshot)) +self._update_circuits( + {cid: SpanPanelCircuit.from_snapshot(cs) for cid, cs in snapshot.circuits.items()} +) +if battery_option_enabled: + self._update_storage_battery(SpanPanelStorageBattery.from_snapshot(snapshot)) +``` -### `SpanPanelApi.update()` — call `get_snapshot()` instead of four methods +### `from_snapshot()` factories -Replace the four individual calls with one: +Each domain object gained a `from_snapshot()` classmethod. Gen3-only fields +(e.g. `main_voltage_v`) are populated if present; Gen2-only fields (energy, +relay state, DSM) default to `None` / `""` / `0.0` when absent. Entity +classes that read those fields are already gated on the corresponding +`PanelCapability` flag and are never created for Gen3. -```python -# Before -status = await self._client.get_status() -panel = await self._client.get_panel_state() -circs = await self._client.get_circuits() -batt = await self._client.get_storage_soe() - -# After -snapshot = await self._client.get_snapshot() -``` +--- -`SpanPanelClient.get_snapshot()` already makes those same four calls internally — -this is a refactor, not a behaviour change. +## Phase 2b — Gen3 Runtime Wiring (Complete) -### `SpanPanel` — populate from `SpanPanelSnapshot` +Depends on Phase 2a being complete. -`SpanPanel` currently holds the four OpenAPI response objects. Replace with a -single `SpanPanelSnapshot` (or equivalent fields derived from it): +### `span_panel_api.py` — Gen3 client creation + +`_create_client()` instantiates `SpanGrpcClient` when +`panel_generation == "gen3"`: ```python -self._snapshot = snapshot # SpanPanelSnapshot -self.main_power_w = snapshot.main_power_w -self.grid_power_w = snapshot.grid_power_w -self.battery_soe = snapshot.battery_soe -self.dsm_state = snapshot.dsm_state -# ... etc. +if self._panel_generation == "gen3": + from span_panel_api.grpc import SpanGrpcClient + self._client = SpanGrpcClient(host=self.host) + return ``` -### `SpanPanelCircuit` — wrap `SpanCircuitSnapshot` +`setup()` for Gen3 calls `connect()` then `start_streaming()` on the gRPC +client. `_ensure_authenticated()` is a no-op for Gen3 (no JWT). -`SpanPanelCircuit` currently wraps the OpenAPI `Circuit` type. Redirect it to -wrap `SpanCircuitSnapshot` instead, mapping field names as needed: +`register_push_callback()` was added to expose callback registration without +requiring callers to access the private `_client`: -| Old (`Circuit` field) | New (`SpanCircuitSnapshot` field) | -|-----------------------|----------------------------------| -| `instantPowerW` | `power_w` | -| `name` | `name` | -| `relayState` | `relay_state` | -| `priority` | `priority` | -| `tabs` | `tabs` | -| `energyAccumImportWh` | `energy_consumed_wh` | -| `energyAccumExportWh` | `energy_produced_wh` | +```python +def register_push_callback(self, cb: Callable[[], None]) -> Callable[[], None] | None: + if self._client is None or not isinstance(self._client, StreamingCapableProtocol): + return None + return cast(StreamingCapableProtocol, self._client).register_callback(cb) +``` -Entity classes need no changes — they continue reading from `SpanPanelCircuit` -properties. Only the backing field source changes. +### `coordinator.py` — Push-streaming extensions to `SpanPanelCoordinator` ---- +Rather than a separate coordinator class, push-streaming behaviour was folded +into the existing `SpanPanelCoordinator` via capability detection at init time: -## Phase 2b — Gen3 Runtime Wiring (Gen3 hardware required) +```python +is_push_streaming = PanelCapability.PUSH_STREAMING in span_panel.api.capabilities -Depends on Phase 2a being complete. The entity data path must already read from -`SpanCircuitSnapshot`-backed properties before Gen3 data can flow through it. +if is_push_streaming: + update_interval = None # disable polling timer for Gen3 +else: + update_interval = timedelta(seconds=scan_interval_seconds) -### `SpanPanelApi._create_client()` — Gen3 branch +super().__init__(..., update_interval=update_interval) -```python -def _create_client(self) -> SpanPanelClientProtocol: - if self._config.get(CONF_PANEL_GENERATION) == "gen3": - from span_panel_api.grpc import SpanGrpcClient # pylint: disable=import-outside-toplevel - from span_panel_api.grpc.const import DEFAULT_GRPC_PORT # pylint: disable=import-outside-toplevel - return SpanGrpcClient(host=self._host, port=DEFAULT_GRPC_PORT) - return SpanPanelClient(host=self._host, ...) +if is_push_streaming: + self._register_push_callback() ``` -`_client` is widened to `SpanPanelClientProtocol | None`. All callers that -currently rely on `SpanPanelClient`-specific methods (authenticate, etc.) must -be guarded by `isinstance(self._client, AuthCapableProtocol)`. +Key methods added: -### `coordinator.py` — `SpanPanelPushCoordinator` +- `_register_push_callback()` — calls `span_panel.api.register_push_callback(self._on_push_data)` +- `_on_push_data()` — sync callback; guards against concurrent tasks with `_push_update_pending` flag; schedules `_async_push_update` +- `_async_push_update()` — calls `span_panel.update()` then `async_set_updated_data(span_panel)` +- `async_shutdown()` override — unregisters the push callback before delegating to `super()` -```python -class SpanPanelPushCoordinator(DataUpdateCoordinator): - async def async_setup(self) -> None: - assert isinstance(self._api.client, StreamingCapableProtocol) - self._unsub = self._api.client.register_callback(self._on_push) - await self._api.client.start_streaming() - - def _on_push(self) -> None: - # get_snapshot() on Gen3 is a cheap in-memory conversion — no I/O - snapshot = asyncio.get_event_loop().run_until_complete( - self._api.client.get_snapshot() - ) - self.async_set_updated_data(snapshot) - - async def async_teardown(self) -> None: - if self._unsub: - self._unsub() - await self._api.client.stop_streaming() -``` +**Rationale for extending vs. subclassing**: A single coordinator class is +easier to maintain and avoids duplicating all the migration/reload logic. The +`update_interval=None` + push-callback approach is the idiomatic HA pattern +for push-driven coordinators. + +### `__init__.py` — `async_unload_entry` cleanup fix + +`async_unload_entry` was looking for `coordinator.span_panel_api` (which does +not exist). Fixed to `coordinator.span_panel` so `span_panel.close()` is +reliably called during unload, stopping the gRPC streaming task and closing +the channel. -### `__init__.py` — choose coordinator at setup time +### `__init__.py` — Migration stamps `panel_generation` on Gen2 upgrades + +In `async_migrate_entry` (v1 → v2), `CONF_PANEL_GENERATION: "gen2"` is added +to the config entry data if absent. All v1 entries pre-date Gen3 support and +are definitively Gen2: ```python -caps = span_panel.api.capabilities -if PanelCapability.PUSH_STREAMING in caps: - coordinator = SpanPanelPushCoordinator(hass, span_panel.api) - await coordinator.async_setup() -else: - coordinator = SpanPanelCoordinator(hass, span_panel.api) - await coordinator.async_config_entry_first_refresh() +migrated_data = dict(config_entry.data) +if CONF_PANEL_GENERATION not in migrated_data: + migrated_data[CONF_PANEL_GENERATION] = "gen2" ``` -### `SpanGrpcClient` hardware validation +### `span_panel_api.py` — Simulation always uses Gen2 transport -Before wiring the runtime path, validate the library client against a real panel: +Gen3 has no simulation infrastructure; simulation is a `SpanPanelClient` (Gen2 REST) feature only. +Rather than blocking the combination in the UI, `SpanPanelApi.__init__` silently normalises +`_panel_generation` to `"gen2"` whenever `simulation_mode=True`: -- `connect()` populates circuits with correct IIDs and names -- `Subscribe` stream delivers notifications at expected cadence -- `_decode_circuit_metrics()` produces correct power/voltage values -- Dual-phase circuit detection works correctly -- `get_snapshot()` returns consistent data during active streaming +```python +self._panel_generation = "gen2" if simulation_mode else panel_generation +``` -See `span-panel-api/docs/Dev/grpc-transport-design.md` → "Hardware Validation -Required" for the full validation checklist. +This means a user can leave the generation dropdown at any value and check the simulator box — +the correct Gen2 transport is used automatically. No config flow guard or translation error +key is needed. -### Sensors — Gen3-only power metrics +### `sensors/factory.py` + `sensor_definitions.py` — Gen3-only power metrics Gen3 exposes per-circuit `voltage_v`, `current_a`, `apparent_power_va`, -`reactive_power_var`, `frequency_hz`, `power_factor` — fields that have no Gen2 -equivalent. These are added as new entity classes that read directly from -`SpanCircuitSnapshot`, created only when the field is not `None` in the first -snapshot: +`reactive_power_var`, `frequency_hz`, `power_factor` — fields that have no +Gen2 equivalent. These are defined in `CIRCUIT_GEN3_SENSORS` and +`PANEL_GEN3_SENSORS`, both gated on `PanelCapability.PUSH_STREAMING`: ```python -first_circuit = next(iter(snapshot.circuits.values()), None) -if first_circuit and first_circuit.voltage_v is not None: - entities.extend(build_gen3_circuit_sensors(snapshot)) +if PanelCapability.PUSH_STREAMING in capabilities: + entities.extend(create_gen3_circuit_sensors(coordinator, circuits)) + entities.extend(create_panel_gen3_sensors(coordinator)) ``` -Sensors that exist on both generations (circuit power, panel power) need no new -entity classes — after Phase 2a they already read from `SpanCircuitSnapshot`. - --- ## Open Questions @@ -351,11 +327,11 @@ entity classes — after Phase 2a they already read from `SpanCircuitSnapshot`. | # | Question | Decision | |---|----------|----------| | 1 | Where to store detected generation? | `entry.data` as `CONF_PANEL_GENERATION` ✅ | -| 2 | Push coordinator: separate class or flag? | Separate class (`SpanPanelPushCoordinator`) — Phase 2b ⏳ | +| 2 | Push coordinator: separate class or flag? | Flag + extensions in existing `SpanPanelCoordinator`; `update_interval=None` disables polling ✅ | | 3 | Gen3 sensors: existing file with capability-gates or new file? | Capability-gates in existing `sensors/factory.py` ✅ | -| 4 | `get_snapshot()` replace or coexist with individual Gen2 calls? | Replace — `update()` calls `get_snapshot()` exclusively (Phase 2a) ✅ | +| 4 | `get_snapshot()` replace or coexist with individual Gen2 calls? | Replace — `update()` calls `get_snapshot()` exclusively ✅ | | 5 | Minimum `span-panel-api` version in `manifest.json`? | `~=1.1.15` with `[grpc]` extra ✅ | -| 6 | Gen3 entity architecture: separate classes, translation layer, or shared base? | Shared base via `SpanCircuitSnapshot` (Phase 2a migration eliminates the choice — existing entity classes work unchanged for overlapping metrics) ✅ | +| 6 | Gen3 entity architecture: separate classes, translation layer, or shared base? | Shared base via `SpanCircuitSnapshot`; Gen3-only metrics in dedicated sensor definitions gated on `PUSH_STREAMING` ✅ | --- @@ -379,13 +355,22 @@ entity classes — after Phase 2a they already read from `SpanCircuitSnapshot`. ✅ span_panel_data.py — from_snapshot(SpanPanelSnapshot) factory ✅ span_panel_hardware_status.py — from_snapshot(SpanPanelSnapshot) factory ✅ span_panel_storage_battery.py — from_snapshot(SpanPanelSnapshot) factory - Phase 2b pending (Gen3 hardware required, depends on 2a): - ⏳ span_panel_api.py — _create_client() Gen3 branch; widen _client type - ⏳ coordinator.py — SpanPanelPushCoordinator - ⏳ __init__.py — coordinator selection by capability - ⏳ sensors/factory.py — Gen3-only sensor entities (voltage, current, etc.) + Phase 2b complete: + ✅ span_panel_api.py — _create_client() Gen3 branch; register_push_callback() + ✅ coordinator.py — push-streaming extensions; _register_push_callback(), + _on_push_data(), _async_push_update(), async_shutdown() + ✅ __init__.py — async_unload_entry cleanup fix; migration stamps panel_generation + ✅ span_panel_api.py — simulation normalises _panel_generation to "gen2" + ✅ config_flow.py — Gen3 + simulation no longer needs a guard (normalised at API layer) + ✅ sensor_definitions.py — CIRCUIT_GEN3_SENSORS, PANEL_GEN3_SENSORS + ✅ sensors/factory.py — Gen3-only sensor creation gated on PUSH_STREAMING + Circuit IID mapping bug fixed (span-panel-api grpc/client.py): + ✅ Removed hardcoded METRIC_IID_OFFSET=27 — wrong for MLO48 (reported in PR #169) + ✅ _parse_instances() now pairs trait 16 / trait 26 IIDs by sorted position + ✅ CircuitInfo.name_iid stores trait 16 IID for correct GetRevision calls + ✅ _metric_iid_to_circuit reverse map enables O(1) streaming lookup ↓ - → PR review + → PR review + Gen3 hardware validation ``` The library branch must be merged and published **before** this integration @@ -398,17 +383,16 @@ branch can be merged, since the integration depends on the new library API. | File | Change | Status | |------|--------|--------| | `custom_components/span_panel/const.py` | Added `CONF_PANEL_GENERATION` | ✅ Done | -| `custom_components/span_panel/span_panel_api.py` | Added `capabilities` property | ✅ Done | -| `custom_components/span_panel/__init__.py` | Capability-gated platform loading; store `active_platforms` per entry | ✅ Done | -| `custom_components/span_panel/config_flow.py` | Generation dropdown; `async_step_gen3_setup`; store generation in entry data | ✅ Done | -| `custom_components/span_panel/sensors/factory.py` | Capability-gated sensor groups (DSM, energy, hardware, battery, solar) | ✅ Done | +| `custom_components/span_panel/span_panel_api.py` | `capabilities` property; `_create_client()` Gen3 branch; `register_push_callback()`; Gen3 `setup()`/`ping()`/`close()` paths | ✅ Done | +| `custom_components/span_panel/__init__.py` | Capability-gated platform loading; `async_unload_entry` fix; migration stamps `panel_generation` | ✅ Done | +| `custom_components/span_panel/config_flow.py` | Generation dropdown; `async_step_gen3_setup` | ✅ Done | +| `custom_components/span_panel/sensors/factory.py` | Capability-gated sensor groups; Gen3-only sensor creation | ✅ Done | +| `custom_components/span_panel/sensor_definitions.py` | `CIRCUIT_GEN3_SENSORS`, `PANEL_GEN3_SENSORS` | ✅ Done | | `custom_components/span_panel/manifest.json` | Version 1.3.2; `span-panel-api[grpc]~=1.1.15` | ✅ Done | -| `custom_components/span_panel/span_panel_api.py` | Added `get_snapshot()` (Phase 2a); widen `_client` to protocol type (Phase 2b) | ✅ Phase 2a done / ⏳ Phase 2b | | `custom_components/span_panel/span_panel.py` | `update()` calls `get_snapshot()`; all domain objects from snapshot | ✅ Done | -| `custom_components/span_panel/span_panel_circuit.py` | Added `from_snapshot(SpanCircuitSnapshot)` factory | ✅ Done | -| `custom_components/span_panel/span_panel_data.py` | Added `from_snapshot(SpanPanelSnapshot)` factory | ✅ Done | -| `custom_components/span_panel/span_panel_hardware_status.py` | Added `from_snapshot(SpanPanelSnapshot)` factory | ✅ Done | -| `custom_components/span_panel/span_panel_storage_battery.py` | Added `from_snapshot(SpanPanelSnapshot)` factory | ✅ Done | -| `custom_components/span_panel/coordinator.py` | Add `SpanPanelPushCoordinator`; coordinator selection in setup | ⏳ Phase 2b | -| `custom_components/span_panel/__init__.py` | Coordinator selection by `PUSH_STREAMING` capability | ⏳ Phase 2b | -| `custom_components/span_panel/sensors/factory.py` | Gen3-only sensor entities (voltage, current, apparent/reactive power, frequency, power factor) | ⏳ Phase 2b | +| `custom_components/span_panel/span_panel_circuit.py` | `from_snapshot(SpanCircuitSnapshot)` factory | ✅ Done | +| `custom_components/span_panel/span_panel_data.py` | `from_snapshot(SpanPanelSnapshot)` factory; Gen3 main feed fields | ✅ Done | +| `custom_components/span_panel/span_panel_hardware_status.py` | `from_snapshot(SpanPanelSnapshot)` factory | ✅ Done | +| `custom_components/span_panel/span_panel_storage_battery.py` | `from_snapshot(SpanPanelSnapshot)` factory | ✅ Done | +| `custom_components/span_panel/coordinator.py` | Push-streaming extensions: capability detection, `update_interval=None` for Gen3, push callback methods, `async_shutdown()` | ✅ Done | +| `custom_components/span_panel/translations/` | No simulation-specific strings needed | ✅ Done | From f3450c0b3b3d3db6d1a7916122e4f14f902ff835 Mon Sep 17 00:00:00 2001 From: cayossarian Date: Tue, 17 Feb 2026 15:47:22 -0800 Subject: [PATCH 06/13] docs: add Developer Testing Setup section to Gen3 integration plan Covers editable install workflow, local HA core and Docker container options, debug logging config, and diagnostic symptom table. --- docs/dev/gen3-grpc-integration-plan.md | 76 ++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/docs/dev/gen3-grpc-integration-plan.md b/docs/dev/gen3-grpc-integration-plan.md index a571981..36bac3d 100644 --- a/docs/dev/gen3-grpc-integration-plan.md +++ b/docs/dev/gen3-grpc-integration-plan.md @@ -396,3 +396,79 @@ branch can be merged, since the integration depends on the new library API. | `custom_components/span_panel/span_panel_storage_battery.py` | `from_snapshot(SpanPanelSnapshot)` factory | ✅ Done | | `custom_components/span_panel/coordinator.py` | Push-streaming extensions: capability detection, `update_interval=None` for Gen3, push callback methods, `async_shutdown()` | ✅ Done | | `custom_components/span_panel/translations/` | No simulation-specific strings needed | ✅ Done | + +--- + +## Developer Testing Setup + +This section is for developers who need to test Gen3 gRPC changes without publishing a new +`span-panel-api` release between every fix. Use an **editable install** so library changes take +effect on the next integration reload. + +### Editable Library Install + +Both repos must be cloned locally. Inside whichever Python environment HA is running in: + +```bash +# Install the library in editable mode (run once) +pip install -e /path/to/span-panel-api[grpc] + +# Confirm — Location must be a file path, not site-packages +pip show span-panel-api +``` + +After this, editing `src/span_panel_api/grpc/client.py` or `grpc/const.py` and reloading the +integration (HA UI → Settings → Devices & Services → SPAN Panel → ⋮ → Reload) is sufficient to +test decode changes — no reinstall or HA restart required. + +### HA Deployment Options + +#### Local HA core (fastest loop) + +```bash +python -m venv ha-venv && source ha-venv/bin/activate +pip install homeassistant +pip install -e /path/to/span-panel-api[grpc] +ln -s /path/to/span/custom_components/span_panel ~/ha-config/custom_components/span_panel +hass -c ~/ha-config +``` + +#### HA in Docker (Home Assistant Container) + +```bash +# Start HA with both repos volume-mounted +docker run -d --name homeassistant \ + -v /path/to/span-panel-api:/span-panel-api \ + -v /path/to/span/custom_components/span_panel:/config/custom_components/span_panel \ + -v ~/ha-config:/config --network host \ + ghcr.io/home-assistant/home-assistant:stable + +# Install editable library inside the container +docker exec homeassistant pip install -e /span-panel-api[grpc] +docker restart homeassistant +``` + +The editable install persists through container restarts. If the container is removed and recreated, re-run the `docker exec pip install` step. + +### Enable Debug Logging + +Add to `configuration.yaml`: + +```yaml +logger: + default: warning + logs: + custom_components.span_panel: debug +``` + +### Diagnostic Symptom Table + +| Symptom | Where to look | +| --- | --- | +| No circuits discovered | `grpc/client.py` → `_parse_instances()` | +| Circuits found but power stays 0 | `grpc/client.py` → `_decode_and_store_metric()` | +| Circuit names wrong or swapped | `grpc/client.py` → `_get_circuit_name_by_iid()`, `CircuitInfo.name_iid` | +| No push updates (entities frozen) | `grpc/client.py` → `_streaming_loop()` | +| Connection refused | `grpc/const.py` → port 50065, `VENDOR_SPAN`, `PRODUCT_GEN3_PANEL` | + +See the library design doc (`span-panel-api/docs/dev/grpc-transport-design.md` → Developer Setup) for the full setup guide and log message reference. From 97b4e3df5035b529a4c753c98950ba3365dcbb07 Mon Sep 17 00:00:00 2001 From: cayossarian Date: Tue, 17 Feb 2026 16:11:00 -0800 Subject: [PATCH 07/13] docs: add Gen3 PR #169 handoff notes and v1.3.2 changelog entry --- CHANGELOG.md | 20 +++++++ docs/dev/gen3-pr169-handoff.md | 104 +++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 docs/dev/gen3-pr169-handoff.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 6191ed9..a9ad7d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.3.2] - Unreleased + +### ✨ New Features + +- **Gen3 panel support (MAIN40 / MLO48)**: Real-time power monitoring for Gen3 gRPC panels via + push-streaming. Circuit and panel power sensors are created automatically; features not + available on Gen3 (relay control, energy history, battery, priorities) are suppressed. + Panel generation is selected during config flow (auto-detect / Gen2 / Gen3). + Thanks to @Griswoldlabs for the Gen3 implementation (PR #169). + +### 🔧 Improvements + +- **Capability-gated platform loading**: Entity platforms (switch, select, battery, solar) now + load only when the panel reports the corresponding capability. Gen2 behavior is unchanged. +- **Unified snapshot model**: All panel data flows through a single `SpanPanelSnapshot` / + `SpanCircuitSnapshot` model regardless of transport, removing all direct OpenAPI type + dependencies from integration code above the library boundary. +- **Push-streaming coordinator**: Gen3 panels drive entity updates via gRPC push callbacks + rather than a polling timer. The coordinator self-configures based on panel capabilities. + ## [1.3.1] - 2026-01-19 ### 🐛 Bug Fixes diff --git a/docs/dev/gen3-pr169-handoff.md b/docs/dev/gen3-pr169-handoff.md new file mode 100644 index 0000000..cf5c623 --- /dev/null +++ b/docs/dev/gen3-pr169-handoff.md @@ -0,0 +1,104 @@ +# Gen3 gRPC — PR #169 Handoff Notes + +This document is for @Griswoldlabs. It describes what happened architecturally after PR #169 +was filed and what is needed to take the work forward. + +The refactoring allows clean integration of the grpc changes you made and the intent is for you to own/change these branches as necessary and create a PR once +you are comfortable. Before we merge into main I will do one more sanity test to ensure gen2 still works. The cuurrent branches have not broken gen2 thus far. + +I will send an invite for the organization and give you admin rights on both repos. We use Poetry for dev, see the notes in the readme about dev. + +Thanks! + +--- + +## Branches + +| Repo | Branch | Purpose | +| --- | --- | --- | +| `span-panel-api` | [`grpc_addition`](https://github.com/SpanPanel/span-panel-api/tree/grpc_addition) | gRPC transport library — `SpanGrpcClient`, protocol/capability abstraction, unified snapshot models | +| `span` | [`gen3-grpc-integration`](https://github.com/SpanPanel/span/tree/gen3-grpc-integration) | Integration rewritten to use the library; no Gen3 transport code remains in the integration | + +--- + +## What Changed + +The gRPC transport code was moved out of the integration and into `span-panel-api`, where it +belongs architecturally. Your PR's `gen3/` directory was the starting point for the library +implementation. When this work merges, the integration will contain no transport code — only +calls through the library's abstraction layer. + +Key points: + +- `SpanGrpcClient` lives in `span-panel-api/src/span_panel_api/grpc/client.py` +- `PanelCapability` flags gate entity platform loading at setup time — Gen3 loads power + sensors only (no relay switches, no energy history, no battery) +- `SpanPanelCoordinator` self-configures for push-streaming vs polling based on capabilities; + no polling timer runs for Gen3 +- The circuit IID mapping bug (reported by @cecilkootz) is addressed with positional pairing + plus a `_metric_iid_to_circuit` reverse map built at connect time — see below for the + outstanding issue + +--- + +## Outstanding Issue — Name / Metric IID Count Mismatch + +@cecilkootz's debug log from the MLO48 shows: + +```text +Discovered 31 name instances (trait 16) and 36 metric instances (trait 26, excl main feed) +``` + +The current positional-pairing approach assumes both IID lists have the same length. On this +panel they do not — 31 name instances vs 36 metric instances. The cause is not yet confirmed +but likely candidates are: + +- Dual-phase circuits occupy two trait-26 IIDs but share one trait-16 name entry +- Some trait-26 IIDs belong to something other than individual circuits + +This is a known open issue. @Griswoldlabs is aware of it. It cannot be resolved without live +Gen3 hardware to inspect what the extra metric IIDs represent. + +--- + +## Planning Docs - these describe the refactoring that took place + +| Document | Location | +| --- | --- | +| gRPC transport design (library) | [`span-panel-api/docs/Dev/grpc-transport-design.md`](https://github.com/SpanPanel/span-panel-api/blob/grpc_addition/docs/Dev/grpc-transport-design.md) | +| Gen3 integration plan (integration) | [`span/docs/dev/gen3-grpc-integration-plan.md`](https://github.com/SpanPanel/span/blob/gen3-grpc-integration/docs/dev/gen3-grpc-integration-plan.md) | + +--- + +## Developer Setup + +Full setup instructions are in the +[Developer Setup section of grpc-transport-design.md](https://github.com/SpanPanel/span-panel-api/blob/grpc_addition/docs/Dev/grpc-transport-design.md#developer-setup-for-hardware-testing) +and the +[Developer Testing Setup section of gen3-grpc-integration-plan.md](https://github.com/SpanPanel/span/blob/gen3-grpc-integration/docs/dev/gen3-grpc-integration-plan.md#developer-testing-setup). + +Short version — install the library in editable mode inside the HA Python environment: + +```bash +pip install -e /path/to/span-panel-api[grpc] +pip show span-panel-api # Location must be a file path, not site-packages +``` + +After the editable install, edit `src/span_panel_api/grpc/client.py` or `grpc/const.py` and +reload the integration in HA UI (Settings → Devices & Services → SPAN Panel → ⋮ → Reload). +No reinstall or HA restart is needed between iterations. + +--- + +## What Needs Hardware Validation + +| Symptom to confirm | File | What to check | +| --- | --- | --- | +| Circuit count correct | `grpc/client.py` → `_parse_instances()` | Does `sorted(set(...))` dedup give the right count on MAIN40 and MLO48? | +| Name / metric mismatch | `grpc/client.py` → `_parse_instances()` | What do the extra metric IIDs represent on the MLO48? | +| Power readings live | `grpc/client.py` → `_decode_and_store_metric()` | Values update within seconds of real load changes | +| Main feed correct | `grpc/client.py` → `_decode_main_feed()` | Field 14 in `Subscribe` contains main feed metrics on both panel models | +| Dual-phase detection | `grpc/client.py` → `_decode_circuit_metrics()` | Voltage threshold and per-leg fields correct | + +Decoder fixes go in `grpc/client.py` and `grpc/const.py` in the library repo. The integration +itself should not need changes for hardware-specific tuning. From fda5aa0f4f4cbbc0c649fd197f80afd92652d6e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 18 Feb 2026 22:21:42 -0500 Subject: [PATCH 08/13] fix: replace Gen3 push callbacks with polling timer to prevent recorder overload Gen3 gRPC stream pushes data every ~1s. The previous callback-driven approach forwarded each notification to HA as a state write, causing CPU/disk spikes (reported by cecilkootz on MLO 48). Now both Gen2 and Gen3 use the same polling timer (default 15s). The gRPC stream still runs in the background keeping the client buffer fresh; the coordinator simply reads the latest snapshot at each scan interval. Co-Authored-By: Claude Opus 4.6 --- custom_components/span_panel/coordinator.py | 120 +++++--------------- 1 file changed, 30 insertions(+), 90 deletions(-) diff --git a/custom_components/span_panel/coordinator.py b/custom_components/span_panel/coordinator.py index dbedcdd..c0b3059 100644 --- a/custom_components/span_panel/coordinator.py +++ b/custom_components/span_panel/coordinator.py @@ -2,7 +2,6 @@ from __future__ import annotations -from collections.abc import Callable from datetime import timedelta import logging from time import time as _epoch_time @@ -62,49 +61,43 @@ def __init__( self._panel_offline = False # Track last grace period value for comparison self._last_grace_period = config_entry.options.get(ENERGY_REPORTING_GRACE_PERIOD, 15) - # Push-streaming support (Gen3 gRPC panels) - self._push_unregister: Callable[[], None] | None = None - self._push_update_pending: bool = False - - # Detect Gen3 push-streaming panels before selecting update interval. + # Detect Gen3 push-streaming panels (for logging only — both use polling). is_push_streaming = PanelCapability.PUSH_STREAMING in span_panel.api.capabilities - if is_push_streaming: - # Gen3: disable polling timer; updates are driven by gRPC push callbacks. - update_interval: timedelta | None = None - _LOGGER.info( - "Span Panel coordinator: Gen3 push-streaming mode — polling timer disabled" - ) - else: - # Gen2: compute polling interval from config options. - raw_scan_interval = config_entry.options.get( - CONF_SCAN_INTERVAL, int(DEFAULT_SCAN_INTERVAL.total_seconds()) - ) - - try: - scan_interval_seconds = int(float(raw_scan_interval)) - except (TypeError, ValueError): - scan_interval_seconds = int(DEFAULT_SCAN_INTERVAL.total_seconds()) + # Both Gen2 and Gen3 use a polling timer to push data to HA. + # For Gen3, the gRPC stream keeps the client buffer fresh every ~1s + # in the background; the coordinator reads it at the scan interval. + # This prevents overwhelming HA's recorder with 1/s state writes. + raw_scan_interval = config_entry.options.get( + CONF_SCAN_INTERVAL, int(DEFAULT_SCAN_INTERVAL.total_seconds()) + ) - if scan_interval_seconds < 5: - _LOGGER.debug( - "Configured scan interval %s is below minimum; clamping to 5 seconds", - scan_interval_seconds, - ) - scan_interval_seconds = 5 + try: + scan_interval_seconds = int(float(raw_scan_interval)) + except (TypeError, ValueError): + scan_interval_seconds = int(DEFAULT_SCAN_INTERVAL.total_seconds()) - if str(raw_scan_interval) != str(scan_interval_seconds): - _LOGGER.debug( - "Coerced scan interval option from raw=%s to %s seconds", - raw_scan_interval, - scan_interval_seconds, - ) + if scan_interval_seconds < 5: + _LOGGER.debug( + "Configured scan interval %s is below minimum; clamping to 5 seconds", + scan_interval_seconds, + ) + scan_interval_seconds = 5 - _LOGGER.info( - "Span Panel coordinator: update interval set to %s seconds", + if str(raw_scan_interval) != str(scan_interval_seconds): + _LOGGER.debug( + "Coerced scan interval option from raw=%s to %s seconds", + raw_scan_interval, scan_interval_seconds, ) - update_interval = timedelta(seconds=scan_interval_seconds) + + mode_label = "Gen3 push-buffered" if is_push_streaming else "Gen2 polling" + _LOGGER.info( + "Span Panel coordinator: %s mode, update interval %s seconds", + mode_label, + scan_interval_seconds, + ) + update_interval: timedelta | None = timedelta(seconds=scan_interval_seconds) super().__init__( hass, @@ -117,10 +110,6 @@ def __init__( # Ensure config_entry is properly set after super().__init__ self.config_entry = config_entry - # Register push callback now that the event loop (hass) is available. - if is_push_streaming: - self._register_push_callback() - @property def panel_offline(self) -> bool: """Return True if the panel is currently offline/unreachable.""" @@ -134,55 +123,6 @@ def request_reload(self) -> None: """Request a reload of the integration.""" self._reload_requested = True - # ------------------------------------------------------------------ - # Gen3 push-streaming support - # ------------------------------------------------------------------ - - def _register_push_callback(self) -> None: - """Register a callback with the Gen3 gRPC streaming client. - - The callback fires each time the gRPC stream pushes new panel data. - It schedules an async task on the HA event loop to update coordinator - data without blocking the streaming loop. - """ - unregister = self.span_panel.api.register_push_callback(self._on_push_data) - if unregister is None: - _LOGGER.warning("Gen3 push streaming requested but client does not support callbacks") - return - self._push_unregister = unregister - _LOGGER.debug("Registered Gen3 push-streaming coordinator callback") - - def _on_push_data(self) -> None: - """Sync callback invoked by the gRPC stream on each new notification. - - Guards against concurrent update tasks: if one is already pending - we skip scheduling another — the in-flight task will read the latest - data when it runs. - """ - if self._push_update_pending: - return - self._push_update_pending = True - self.hass.async_create_task(self._async_push_update()) - - async def _async_push_update(self) -> None: - """Apply incoming push data to coordinator state and notify listeners.""" - try: - await self.span_panel.update() - self._panel_offline = False - self.async_set_updated_data(self.span_panel) - except Exception as err: # pylint: disable=broad-exception-caught - self._panel_offline = True - _LOGGER.warning("Gen3 push update failed: %s", err) - finally: - self._push_update_pending = False - - async def async_shutdown(self) -> None: - """Shutdown coordinator and unregister Gen3 push-streaming callback.""" - if self._push_unregister is not None: - self._push_unregister() - self._push_unregister = None - await super().async_shutdown() - async def _async_update_data(self) -> SpanPanel: """Fetch data from API endpoint.""" try: From 650e1d5b125fa6bb77fc1ad3df9028dc8af3dc91 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 18 Feb 2026 23:47:58 -0500 Subject: [PATCH 09/13] chore: improve sensor setup logging with entity count and serial Replace warning-level debug log with info-level log that shows the number of circuits, entities created, and the resolved serial number. Helps diagnose sensor registration issues during setup. Co-Authored-By: Claude Opus 4.6 --- custom_components/span_panel/sensor.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/custom_components/span_panel/sensor.py b/custom_components/span_panel/sensor.py index b63bb3c..8495a0f 100644 --- a/custom_components/span_panel/sensor.py +++ b/custom_components/span_panel/sensor.py @@ -63,6 +63,13 @@ async def async_setup_entry( # Create all native sensors (now includes panel, circuit, and solar sensors) entities = create_native_sensors(coordinator, span_panel, config_entry) + _LOGGER.info( + "SENSOR SETUP: %d circuits, %d entities created, serial=%s", + len(span_panel.circuits), + len(entities), + span_panel.status.serial_number, + ) + # Add all native sensor entities async_add_entities(entities) From 9e668bf38ab1a62d1f53fe49f9a7086ad1b011b9 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 19 Feb 2026 10:49:11 -0500 Subject: [PATCH 10/13] style: fix ruff formatting in sensor_definitions.py Co-Authored-By: Claude Opus 4.6 --- .../span_panel/sensor_definitions.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/custom_components/span_panel/sensor_definitions.py b/custom_components/span_panel/sensor_definitions.py index d465184..687770b 100644 --- a/custom_components/span_panel/sensor_definitions.py +++ b/custom_components/span_panel/sensor_definitions.py @@ -259,8 +259,10 @@ class SpanPanelBatterySensorEntityDescription( state_class=SensorStateClass.TOTAL, suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY, - value_fn=lambda panel_data: (panel_data.main_meter_energy_consumed or 0) - - (panel_data.main_meter_energy_produced or 0), + value_fn=lambda panel_data: ( + (panel_data.main_meter_energy_consumed or 0) + - (panel_data.main_meter_energy_produced or 0) + ), ), SpanPanelDataSensorEntityDescription( key="feedthroughNetEnergyWh", @@ -269,8 +271,10 @@ class SpanPanelBatterySensorEntityDescription( state_class=SensorStateClass.TOTAL, suggested_display_precision=2, device_class=SensorDeviceClass.ENERGY, - value_fn=lambda panel_data: (panel_data.feedthrough_energy_consumed or 0) - - (panel_data.feedthrough_energy_produced or 0), + value_fn=lambda panel_data: ( + (panel_data.feedthrough_energy_consumed or 0) + - (panel_data.feedthrough_energy_produced or 0) + ), ), ) @@ -462,9 +466,9 @@ class SpanSolarSensorEntityDescription(SensorEntityDescription): state_class=SensorStateClass.MEASUREMENT, suggested_display_precision=1, device_class=SensorDeviceClass.POWER_FACTOR, - value_fn=lambda circuit: (circuit.power_factor * 100) - if circuit.power_factor is not None - else None, + value_fn=lambda circuit: ( + (circuit.power_factor * 100) if circuit.power_factor is not None else None + ), entity_registry_enabled_default=True, entity_registry_visible_default=True, ), From fe0660c4076621278f0152742b7d8bd2cfacc5fc Mon Sep 17 00:00:00 2001 From: cayossarian Date: Thu, 19 Feb 2026 16:47:33 -0800 Subject: [PATCH 11/13] chore: update Python to 3.14.2 and HA to 2026.2.2 - Bump python constraint to >=3.14.2,<3.15 to match HA core requirement - Pin homeassistant to 2026.2.2 (latest stable with pytest-homeassistant-custom-component support) - Add mashumaro >=3.17.0 floor to prevent poetry under-resolving below HA package_constraints.txt minimum - Drop homeassistant-stubs path-dep approach; restore homeassistant-stubs = 2026.2.2 - Update pyright pythonVersion to 3.14 - Update CI workflows (ci.yml, ci-simulation-example.yml) to python-version: "3.14" - Regenerate poetry.lock --- .github/workflows/ci-simulation-example.yml | 2 +- .github/workflows/ci.yml | 2 +- poetry.lock | 1695 +++++-------------- pyproject.toml | 11 +- 4 files changed, 431 insertions(+), 1279 deletions(-) diff --git a/.github/workflows/ci-simulation-example.yml b/.github/workflows/ci-simulation-example.yml index 1e41d26..0211732 100644 --- a/.github/workflows/ci-simulation-example.yml +++ b/.github/workflows/ci-simulation-example.yml @@ -13,7 +13,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: "3.13" + python-version: "3.14" - name: Install Poetry uses: snok/install-poetry@v1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad08d66..e827a67 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: "3.13" + python-version: "3.14" - name: Install Poetry uses: snok/install-poetry@v1 diff --git a/poetry.lock b/poetry.lock index 1116f8d..fa978ff 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,15 +1,15 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. [[package]] name = "acme" -version = "5.1.0" +version = "5.2.2" description = "ACME protocol implementation in Python" optional = false python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "acme-5.1.0-py3-none-any.whl", hash = "sha256:80e9c315d82302bb97279f4516ff31230d29195ab9d4a6c9411ceec20481b61e"}, - {file = "acme-5.1.0.tar.gz", hash = "sha256:7b97820857d9baffed98bca50ab82bb6a636e447865d7a013a7bdd7972f03cda"}, + {file = "acme-5.2.2-py3-none-any.whl", hash = "sha256:354ef66cf226b2bef02006311778e97123237207b4febe8829ded9860784ee64"}, + {file = "acme-5.2.2.tar.gz", hash = "sha256:7702d5b99149d5cd9cd48a9270c04693e925730c023ca3e1b853ab43746a9d01"}, ] [package.dependencies] @@ -25,18 +25,18 @@ test = ["pytest", "pytest-xdist", "typing-extensions"] [[package]] name = "aiodns" -version = "3.6.1" +version = "4.0.0" description = "Simple DNS resolver for asyncio" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "aiodns-3.6.1-py3-none-any.whl", hash = "sha256:46233ccad25f2037903828c5d05b64590eaa756e51d12b4a5616e2defcbc98c7"}, - {file = "aiodns-3.6.1.tar.gz", hash = "sha256:b0e9ce98718a5b8f7ca8cd16fc393163374bc2412236b91f6c851d066e3324b6"}, + {file = "aiodns-4.0.0-py3-none-any.whl", hash = "sha256:a188a75fb8b2b7862ac8f84811a231402fb74f5b4e6f10766dc8a4544b0cf989"}, + {file = "aiodns-4.0.0.tar.gz", hash = "sha256:17be26a936ba788c849ba5fd20e0ba69d8c46e6273e846eb5430eae2630ce5b1"}, ] [package.dependencies] -pycares = ">=4.9.0,<5" +pycares = ">=5.0.0,<6" [[package]] name = "aiohappyeyeballs" @@ -72,132 +72,132 @@ dev = ["aiohttp (==3.12.15)", "aioresponses (==0.7.8)", "codespell (==2.4.1)", " [[package]] name = "aiohttp" -version = "3.13.2" +version = "3.13.3" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "aiohttp-3.13.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2372b15a5f62ed37789a6b383ff7344fc5b9f243999b0cd9b629d8bc5f5b4155"}, - {file = "aiohttp-3.13.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7f8659a48995edee7229522984bd1009c1213929c769c2daa80b40fe49a180c"}, - {file = "aiohttp-3.13.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:939ced4a7add92296b0ad38892ce62b98c619288a081170695c6babe4f50e636"}, - {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6315fb6977f1d0dd41a107c527fee2ed5ab0550b7d885bc15fee20ccb17891da"}, - {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6e7352512f763f760baaed2637055c49134fd1d35b37c2dedfac35bfe5cf8725"}, - {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e09a0a06348a2dd73e7213353c90d709502d9786219f69b731f6caa0efeb46f5"}, - {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a09a6d073fb5789456545bdee2474d14395792faa0527887f2f4ec1a486a59d3"}, - {file = "aiohttp-3.13.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b59d13c443f8e049d9e94099c7e412e34610f1f49be0f230ec656a10692a5802"}, - {file = "aiohttp-3.13.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:20db2d67985d71ca033443a1ba2001c4b5693fe09b0e29f6d9358a99d4d62a8a"}, - {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:960c2fc686ba27b535f9fd2b52d87ecd7e4fd1cf877f6a5cba8afb5b4a8bd204"}, - {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:6c00dbcf5f0d88796151e264a8eab23de2997c9303dd7c0bf622e23b24d3ce22"}, - {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fed38a5edb7945f4d1bcabe2fcd05db4f6ec7e0e82560088b754f7e08d93772d"}, - {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:b395bbca716c38bef3c764f187860e88c724b342c26275bc03e906142fc5964f"}, - {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:204ffff2426c25dfda401ba08da85f9c59525cdc42bda26660463dd1cbcfec6f"}, - {file = "aiohttp-3.13.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:05c4dd3c48fb5f15db31f57eb35374cb0c09afdde532e7fb70a75aede0ed30f6"}, - {file = "aiohttp-3.13.2-cp310-cp310-win32.whl", hash = "sha256:e574a7d61cf10351d734bcddabbe15ede0eaa8a02070d85446875dc11189a251"}, - {file = "aiohttp-3.13.2-cp310-cp310-win_amd64.whl", hash = "sha256:364f55663085d658b8462a1c3f17b2b84a5c2e1ba858e1b79bff7b2e24ad1514"}, - {file = "aiohttp-3.13.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4647d02df098f6434bafd7f32ad14942f05a9caa06c7016fdcc816f343997dd0"}, - {file = "aiohttp-3.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e3403f24bcb9c3b29113611c3c16a2a447c3953ecf86b79775e7be06f7ae7ccb"}, - {file = "aiohttp-3.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:43dff14e35aba17e3d6d5ba628858fb8cb51e30f44724a2d2f0c75be492c55e9"}, - {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2a9ea08e8c58bb17655630198833109227dea914cd20be660f52215f6de5613"}, - {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53b07472f235eb80e826ad038c9d106c2f653584753f3ddab907c83f49eedead"}, - {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e736c93e9c274fce6419af4aac199984d866e55f8a4cec9114671d0ea9688780"}, - {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ff5e771f5dcbc81c64898c597a434f7682f2259e0cd666932a913d53d1341d1a"}, - {file = "aiohttp-3.13.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3b6fb0c207cc661fa0bf8c66d8d9b657331ccc814f4719468af61034b478592"}, - {file = "aiohttp-3.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:97a0895a8e840ab3520e2288db7cace3a1981300d48babeb50e7425609e2e0ab"}, - {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9e8f8afb552297aca127c90cb840e9a1d4bfd6a10d7d8f2d9176e1acc69bad30"}, - {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed2f9c7216e53c3df02264f25d824b079cc5914f9e2deba94155190ef648ee40"}, - {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:99c5280a329d5fa18ef30fd10c793a190d996567667908bef8a7f81f8202b948"}, - {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ca6ffef405fc9c09a746cb5d019c1672cd7f402542e379afc66b370833170cf"}, - {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:47f438b1a28e926c37632bff3c44df7d27c9b57aaf4e34b1def3c07111fdb782"}, - {file = "aiohttp-3.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9acda8604a57bb60544e4646a4615c1866ee6c04a8edef9b8ee6fd1d8fa2ddc8"}, - {file = "aiohttp-3.13.2-cp311-cp311-win32.whl", hash = "sha256:868e195e39b24aaa930b063c08bb0c17924899c16c672a28a65afded9c46c6ec"}, - {file = "aiohttp-3.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:7fd19df530c292542636c2a9a85854fab93474396a52f1695e799186bbd7f24c"}, - {file = "aiohttp-3.13.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b1e56bab2e12b2b9ed300218c351ee2a3d8c8fdab5b1ec6193e11a817767e47b"}, - {file = "aiohttp-3.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:364e25edaabd3d37b1db1f0cbcee8c73c9a3727bfa262b83e5e4cf3489a2a9dc"}, - {file = "aiohttp-3.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c5c94825f744694c4b8db20b71dba9a257cd2ba8e010a803042123f3a25d50d7"}, - {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba2715d842ffa787be87cbfce150d5e88c87a98e0b62e0f5aa489169a393dbbb"}, - {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:585542825c4bc662221fb257889e011a5aa00f1ae4d75d1d246a5225289183e3"}, - {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39d02cb6025fe1aabca329c5632f48c9532a3dabccd859e7e2f110668972331f"}, - {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e67446b19e014d37342f7195f592a2a948141d15a312fe0e700c2fd2f03124f6"}, - {file = "aiohttp-3.13.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4356474ad6333e41ccefd39eae869ba15a6c5299c9c01dfdcfdd5c107be4363e"}, - {file = "aiohttp-3.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeacf451c99b4525f700f078becff32c32ec327b10dcf31306a8a52d78166de7"}, - {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8a9b889aeabd7a4e9af0b7f4ab5ad94d42e7ff679aaec6d0db21e3b639ad58d"}, - {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fa89cb11bc71a63b69568d5b8a25c3ca25b6d54c15f907ca1c130d72f320b76b"}, - {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8aa7c807df234f693fed0ecd507192fc97692e61fee5702cdc11155d2e5cadc8"}, - {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9eb3e33fdbe43f88c3c75fa608c25e7c47bbd80f48d012763cb67c47f39a7e16"}, - {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9434bc0d80076138ea986833156c5a48c9c7a8abb0c96039ddbb4afc93184169"}, - {file = "aiohttp-3.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff15c147b2ad66da1f2cbb0622313f2242d8e6e8f9b79b5206c84523a4473248"}, - {file = "aiohttp-3.13.2-cp312-cp312-win32.whl", hash = "sha256:27e569eb9d9e95dbd55c0fc3ec3a9335defbf1d8bc1d20171a49f3c4c607b93e"}, - {file = "aiohttp-3.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:8709a0f05d59a71f33fd05c17fc11fcb8c30140506e13c2f5e8ee1b8964e1b45"}, - {file = "aiohttp-3.13.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7519bdc7dfc1940d201651b52bf5e03f5503bda45ad6eacf64dda98be5b2b6be"}, - {file = "aiohttp-3.13.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:088912a78b4d4f547a1f19c099d5a506df17eacec3c6f4375e2831ec1d995742"}, - {file = "aiohttp-3.13.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5276807b9de9092af38ed23ce120539ab0ac955547b38563a9ba4f5b07b95293"}, - {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1237c1375eaef0db4dcd7c2559f42e8af7b87ea7d295b118c60c36a6e61cb811"}, - {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:96581619c57419c3d7d78703d5b78c1e5e5fc0172d60f555bdebaced82ded19a"}, - {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a2713a95b47374169409d18103366de1050fe0ea73db358fc7a7acb2880422d4"}, - {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:228a1cd556b3caca590e9511a89444925da87d35219a49ab5da0c36d2d943a6a"}, - {file = "aiohttp-3.13.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6cde5fba8d7d8c6ac963dbb0256a9854e9fafff52fbcc58fdf819357892c3e"}, - {file = "aiohttp-3.13.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f2bef8237544f4e42878c61cef4e2839fee6346dc60f5739f876a9c50be7fcdb"}, - {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:16f15a4eac3bc2d76c45f7ebdd48a65d41b242eb6c31c2245463b40b34584ded"}, - {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:bb7fb776645af5cc58ab804c58d7eba545a97e047254a52ce89c157b5af6cd0b"}, - {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e1b4951125ec10c70802f2cb09736c895861cd39fd9dcb35107b4dc8ae6220b8"}, - {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:550bf765101ae721ee1d37d8095f47b1f220650f85fe1af37a90ce75bab89d04"}, - {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fe91b87fc295973096251e2d25a811388e7d8adf3bd2b97ef6ae78bc4ac6c476"}, - {file = "aiohttp-3.13.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0c8e31cfcc4592cb200160344b2fb6ae0f9e4effe06c644b5a125d4ae5ebe23"}, - {file = "aiohttp-3.13.2-cp313-cp313-win32.whl", hash = "sha256:0740f31a60848d6edb296a0df827473eede90c689b8f9f2a4cdde74889eb2254"}, - {file = "aiohttp-3.13.2-cp313-cp313-win_amd64.whl", hash = "sha256:a88d13e7ca367394908f8a276b89d04a3652044612b9a408a0bb22a5ed976a1a"}, - {file = "aiohttp-3.13.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2475391c29230e063ef53a66669b7b691c9bfc3f1426a0f7bcdf1216bdbac38b"}, - {file = "aiohttp-3.13.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:f33c8748abef4d8717bb20e8fb1b3e07c6adacb7fd6beaae971a764cf5f30d61"}, - {file = "aiohttp-3.13.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ae32f24bbfb7dbb485a24b30b1149e2f200be94777232aeadba3eecece4d0aa4"}, - {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7f02042c1f009ffb70067326ef183a047425bb2ff3bc434ead4dd4a4a66a2b"}, - {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93655083005d71cd6c072cdab54c886e6570ad2c4592139c3fb967bfc19e4694"}, - {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0db1e24b852f5f664cd728db140cf11ea0e82450471232a394b3d1a540b0f906"}, - {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b009194665bcd128e23eaddef362e745601afa4641930848af4c8559e88f18f9"}, - {file = "aiohttp-3.13.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c038a8fdc8103cd51dbd986ecdce141473ffd9775a7a8057a6ed9c3653478011"}, - {file = "aiohttp-3.13.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:66bac29b95a00db411cd758fea0e4b9bdba6d549dfe333f9a945430f5f2cc5a6"}, - {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4ebf9cfc9ba24a74cf0718f04aac2a3bbe745902cc7c5ebc55c0f3b5777ef213"}, - {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a4b88ebe35ce54205c7074f7302bd08a4cb83256a3e0870c72d6f68a3aaf8e49"}, - {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:98c4fb90bb82b70a4ed79ca35f656f4281885be076f3f970ce315402b53099ae"}, - {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:ec7534e63ae0f3759df3a1ed4fa6bc8f75082a924b590619c0dd2f76d7043caa"}, - {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5b927cf9b935a13e33644cbed6c8c4b2d0f25b713d838743f8fe7191b33829c4"}, - {file = "aiohttp-3.13.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:88d6c017966a78c5265d996c19cdb79235be5e6412268d7e2ce7dee339471b7a"}, - {file = "aiohttp-3.13.2-cp314-cp314-win32.whl", hash = "sha256:f7c183e786e299b5d6c49fb43a769f8eb8e04a2726a2bd5887b98b5cc2d67940"}, - {file = "aiohttp-3.13.2-cp314-cp314-win_amd64.whl", hash = "sha256:fe242cd381e0fb65758faf5ad96c2e460df6ee5b2de1072fe97e4127927e00b4"}, - {file = "aiohttp-3.13.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f10d9c0b0188fe85398c61147bbd2a657d616c876863bfeff43376e0e3134673"}, - {file = "aiohttp-3.13.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e7c952aefdf2460f4ae55c5e9c3e80aa72f706a6317e06020f80e96253b1accd"}, - {file = "aiohttp-3.13.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c20423ce14771d98353d2e25e83591fa75dfa90a3c1848f3d7c68243b4fbded3"}, - {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e96eb1a34396e9430c19d8338d2ec33015e4a87ef2b4449db94c22412e25ccdf"}, - {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:23fb0783bc1a33640036465019d3bba069942616a6a2353c6907d7fe1ccdaf4e"}, - {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e1a9bea6244a1d05a4e57c295d69e159a5c50d8ef16aa390948ee873478d9a5"}, - {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a3d54e822688b56e9f6b5816fb3de3a3a64660efac64e4c2dc435230ad23bad"}, - {file = "aiohttp-3.13.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7a653d872afe9f33497215745da7a943d1dc15b728a9c8da1c3ac423af35178e"}, - {file = "aiohttp-3.13.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:56d36e80d2003fa3fc0207fac644216d8532e9504a785ef9a8fd013f84a42c61"}, - {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:78cd586d8331fb8e241c2dd6b2f4061778cc69e150514b39a9e28dd050475661"}, - {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:20b10bbfbff766294fe99987f7bb3b74fdd2f1a2905f2562132641ad434dcf98"}, - {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9ec49dff7e2b3c85cdeaa412e9d438f0ecd71676fde61ec57027dd392f00c693"}, - {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:94f05348c4406450f9d73d38efb41d669ad6cd90c7ee194810d0eefbfa875a7a"}, - {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:fa4dcb605c6f82a80c7f95713c2b11c3b8e9893b3ebd2bc9bde93165ed6107be"}, - {file = "aiohttp-3.13.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cf00e5db968c3f67eccd2778574cf64d8b27d95b237770aa32400bd7a1ca4f6c"}, - {file = "aiohttp-3.13.2-cp314-cp314t-win32.whl", hash = "sha256:d23b5fe492b0805a50d3371e8a728a9134d8de5447dce4c885f5587294750734"}, - {file = "aiohttp-3.13.2-cp314-cp314t-win_amd64.whl", hash = "sha256:ff0a7b0a82a7ab905cbda74006318d1b12e37c797eb1b0d4eb3e316cf47f658f"}, - {file = "aiohttp-3.13.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7fbdf5ad6084f1940ce88933de34b62358d0f4a0b6ec097362dcd3e5a65a4989"}, - {file = "aiohttp-3.13.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7c3a50345635a02db61792c85bb86daffac05330f6473d524f1a4e3ef9d0046d"}, - {file = "aiohttp-3.13.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0e87dff73f46e969af38ab3f7cb75316a7c944e2e574ff7c933bc01b10def7f5"}, - {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2adebd4577724dcae085665f294cc57c8701ddd4d26140504db622b8d566d7aa"}, - {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e036a3a645fe92309ec34b918394bb377950cbb43039a97edae6c08db64b23e2"}, - {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:23ad365e30108c422d0b4428cf271156dd56790f6dd50d770b8e360e6c5ab2e6"}, - {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1f9b2c2d4b9d958b1f9ae0c984ec1dd6b6689e15c75045be8ccb4011426268ca"}, - {file = "aiohttp-3.13.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a92cf4b9bea33e15ecbaa5c59921be0f23222608143d025c989924f7e3e0c07"}, - {file = "aiohttp-3.13.2-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:070599407f4954021509193404c4ac53153525a19531051661440644728ba9a7"}, - {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:29562998ec66f988d49fb83c9b01694fa927186b781463f376c5845c121e4e0b"}, - {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4dd3db9d0f4ebca1d887d76f7cdbcd1116ac0d05a9221b9dad82c64a62578c4d"}, - {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d7bc4b7f9c4921eba72677cd9fedd2308f4a4ca3e12fab58935295ad9ea98700"}, - {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:dacd50501cd017f8cccb328da0c90823511d70d24a323196826d923aad865901"}, - {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:8b2f1414f6a1e0683f212ec80e813f4abef94c739fd090b66c9adf9d2a05feac"}, - {file = "aiohttp-3.13.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04c3971421576ed24c191f610052bcb2f059e395bc2489dd99e397f9bc466329"}, - {file = "aiohttp-3.13.2-cp39-cp39-win32.whl", hash = "sha256:9f377d0a924e5cc94dc620bc6366fc3e889586a7f18b748901cf016c916e2084"}, - {file = "aiohttp-3.13.2-cp39-cp39-win_amd64.whl", hash = "sha256:9c705601e16c03466cb72011bd1af55d68fa65b045356d8f96c216e5f6db0fa5"}, - {file = "aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca"}, + {file = "aiohttp-3.13.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d5a372fd5afd301b3a89582817fdcdb6c34124787c70dbcc616f259013e7eef7"}, + {file = "aiohttp-3.13.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:147e422fd1223005c22b4fe080f5d93ced44460f5f9c105406b753612b587821"}, + {file = "aiohttp-3.13.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:859bd3f2156e81dd01432f5849fc73e2243d4a487c4fd26609b1299534ee1845"}, + {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dca68018bf48c251ba17c72ed479f4dafe9dbd5a73707ad8d28a38d11f3d42af"}, + {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fee0c6bc7db1de362252affec009707a17478a00ec69f797d23ca256e36d5940"}, + {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c048058117fd649334d81b4b526e94bde3ccaddb20463a815ced6ecbb7d11160"}, + {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:215a685b6fbbfcf71dfe96e3eba7a6f58f10da1dfdf4889c7dd856abe430dca7"}, + {file = "aiohttp-3.13.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2c184bb1fe2cbd2cefba613e9db29a5ab559323f994b6737e370d3da0ac455"}, + {file = "aiohttp-3.13.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:75ca857eba4e20ce9f546cd59c7007b33906a4cd48f2ff6ccf1ccfc3b646f279"}, + {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81e97251d9298386c2b7dbeb490d3d1badbdc69107fb8c9299dd04eb39bddc0e"}, + {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c0e2d366af265797506f0283487223146af57815b388623f0357ef7eac9b209d"}, + {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4e239d501f73d6db1522599e14b9b321a7e3b1de66ce33d53a765d975e9f4808"}, + {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0db318f7a6f065d84cb1e02662c526294450b314a02bd9e2a8e67f0d8564ce40"}, + {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:bfc1cc2fe31a6026a8a88e4ecfb98d7f6b1fec150cfd708adbfd1d2f42257c29"}, + {file = "aiohttp-3.13.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af71fff7bac6bb7508956696dce8f6eec2bbb045eceb40343944b1ae62b5ef11"}, + {file = "aiohttp-3.13.3-cp310-cp310-win32.whl", hash = "sha256:37da61e244d1749798c151421602884db5270faf479cf0ef03af0ff68954c9dd"}, + {file = "aiohttp-3.13.3-cp310-cp310-win_amd64.whl", hash = "sha256:7e63f210bc1b57ef699035f2b4b6d9ce096b5914414a49b0997c839b2bd2223c"}, + {file = "aiohttp-3.13.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b6073099fb654e0a068ae678b10feff95c5cae95bbfcbfa7af669d361a8aa6b"}, + {file = "aiohttp-3.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cb93e166e6c28716c8c6aeb5f99dfb6d5ccf482d29fe9bf9a794110e6d0ab64"}, + {file = "aiohttp-3.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28e027cf2f6b641693a09f631759b4d9ce9165099d2b5d92af9bd4e197690eea"}, + {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b61b7169ababd7802f9568ed96142616a9118dd2be0d1866e920e77ec8fa92a"}, + {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:80dd4c21b0f6237676449c6baaa1039abae86b91636b6c91a7f8e61c87f89540"}, + {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65d2ccb7eabee90ce0503c17716fc77226be026dcc3e65cce859a30db715025b"}, + {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b179331a481cb5529fca8b432d8d3c7001cb217513c94cd72d668d1248688a3"}, + {file = "aiohttp-3.13.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d4c940f02f49483b18b079d1c27ab948721852b281f8b015c058100e9421dd1"}, + {file = "aiohttp-3.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9444f105664c4ce47a2a7171a2418bce5b7bae45fb610f4e2c36045d85911d3"}, + {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:694976222c711d1d00ba131904beb60534f93966562f64440d0c9d41b8cdb440"}, + {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f33ed1a2bf1997a36661874b017f5c4b760f41266341af36febaf271d179f6d7"}, + {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e636b3c5f61da31a92bf0d91da83e58fdfa96f178ba682f11d24f31944cdd28c"}, + {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5d2d94f1f5fcbe40838ac51a6ab5704a6f9ea42e72ceda48de5e6b898521da51"}, + {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2be0e9ccf23e8a94f6f0650ce06042cefc6ac703d0d7ab6c7a917289f2539ad4"}, + {file = "aiohttp-3.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9af5e68ee47d6534d36791bbe9b646d2a7c7deb6fc24d7943628edfbb3581f29"}, + {file = "aiohttp-3.13.3-cp311-cp311-win32.whl", hash = "sha256:a2212ad43c0833a873d0fb3c63fa1bacedd4cf6af2fee62bf4b739ceec3ab239"}, + {file = "aiohttp-3.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:642f752c3eb117b105acbd87e2c143de710987e09860d674e068c4c2c441034f"}, + {file = "aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c"}, + {file = "aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168"}, + {file = "aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d"}, + {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29"}, + {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3"}, + {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d"}, + {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463"}, + {file = "aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc"}, + {file = "aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf"}, + {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033"}, + {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f"}, + {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679"}, + {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423"}, + {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce"}, + {file = "aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a"}, + {file = "aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046"}, + {file = "aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57"}, + {file = "aiohttp-3.13.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5dff64413671b0d3e7d5918ea490bdccb97a4ad29b3f311ed423200b2203e01c"}, + {file = "aiohttp-3.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:87b9aab6d6ed88235aa2970294f496ff1a1f9adcd724d800e9b952395a80ffd9"}, + {file = "aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3"}, + {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf"}, + {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:697753042d57f4bf7122cab985bf15d0cef23c770864580f5af4f52023a56bd6"}, + {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6de499a1a44e7de70735d0b39f67c8f25eb3d91eb3103be99ca0fa882cdd987d"}, + {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37239e9f9a7ea9ac5bf6b92b0260b01f8a22281996da609206a84df860bc1261"}, + {file = "aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0"}, + {file = "aiohttp-3.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fc290605db2a917f6e81b0e1e0796469871f5af381ce15c604a3c5c7e51cb730"}, + {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4021b51936308aeea0367b8f006dc999ca02bc118a0cc78c303f50a2ff6afb91"}, + {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:49a03727c1bba9a97d3e93c9f93ca03a57300f484b6e935463099841261195d3"}, + {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3d9908a48eb7416dc1f4524e69f1d32e5d90e3981e4e37eb0aa1cd18f9cfa2a4"}, + {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2712039939ec963c237286113c68dbad80a82a4281543f3abf766d9d73228998"}, + {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7bfdc049127717581866fa4708791220970ce291c23e28ccf3922c700740fdc0"}, + {file = "aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591"}, + {file = "aiohttp-3.13.3-cp313-cp313-win32.whl", hash = "sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf"}, + {file = "aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e"}, + {file = "aiohttp-3.13.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:ea37047c6b367fd4bd632bff8077449b8fa034b69e812a18e0132a00fae6e808"}, + {file = "aiohttp-3.13.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6fc0e2337d1a4c3e6acafda6a78a39d4c14caea625124817420abceed36e2415"}, + {file = "aiohttp-3.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c685f2d80bb67ca8c3837823ad76196b3694b0159d232206d1e461d3d434666f"}, + {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e377758516d262bde50c2584fc6c578af272559c409eecbdd2bae1601184d6"}, + {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:34749271508078b261c4abb1767d42b8d0c0cc9449c73a4df494777dc55f0687"}, + {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82611aeec80eb144416956ec85b6ca45a64d76429c1ed46ae1b5f86c6e0c9a26"}, + {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2fff83cfc93f18f215896e3a190e8e5cb413ce01553901aca925176e7568963a"}, + {file = "aiohttp-3.13.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbe7d4cecacb439e2e2a8a1a7b935c25b812af7a5fd26503a66dadf428e79ec1"}, + {file = "aiohttp-3.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b928f30fe49574253644b1ca44b1b8adbd903aa0da4b9054a6c20fc7f4092a25"}, + {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5e8fe4de30df199155baaf64f2fcd604f4c678ed20910db8e2c66dc4b11603"}, + {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8542f41a62bcc58fc7f11cf7c90e0ec324ce44950003feb70640fc2a9092c32a"}, + {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5e1d8c8b8f1d91cd08d8f4a3c2b067bfca6ec043d3ff36de0f3a715feeedf926"}, + {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:90455115e5da1c3c51ab619ac57f877da8fd6d73c05aacd125c5ae9819582aba"}, + {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:042e9e0bcb5fba81886c8b4fbb9a09d6b8a00245fd8d88e4d989c1f96c74164c"}, + {file = "aiohttp-3.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2eb752b102b12a76ca02dff751a801f028b4ffbbc478840b473597fc91a9ed43"}, + {file = "aiohttp-3.13.3-cp314-cp314-win32.whl", hash = "sha256:b556c85915d8efaed322bf1bdae9486aa0f3f764195a0fb6ee962e5c71ef5ce1"}, + {file = "aiohttp-3.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9bf9f7a65e7aa20dd764151fb3d616c81088f91f8df39c3893a536e279b4b984"}, + {file = "aiohttp-3.13.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:05861afbbec40650d8a07ea324367cb93e9e8cc7762e04dd4405df99fa65159c"}, + {file = "aiohttp-3.13.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2fc82186fadc4a8316768d61f3722c230e2c1dcab4200d52d2ebdf2482e47592"}, + {file = "aiohttp-3.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0add0900ff220d1d5c5ebbf99ed88b0c1bbf87aa7e4262300ed1376a6b13414f"}, + {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:568f416a4072fbfae453dcf9a99194bbb8bdeab718e08ee13dfa2ba0e4bebf29"}, + {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:add1da70de90a2569c5e15249ff76a631ccacfe198375eead4aadf3b8dc849dc"}, + {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b47b7ba335d2e9b1239fa571131a87e2d8ec96b333e68b2a305e7a98b0bae2"}, + {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4dce1c718e38081c8f35f323209d4c1df7d4db4bab1b5c88a6b4d12b74587"}, + {file = "aiohttp-3.13.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34bac00a67a812570d4a460447e1e9e06fae622946955f939051e7cc895cfab8"}, + {file = "aiohttp-3.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19884d2ee70b06d9204b2727a7b9f983d0c684c650254679e716b0b77920632"}, + {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ca7f2bb6ba8348a3614c7918cc4bb73268c5ac2a207576b7afea19d3d9f64"}, + {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b0d95340658b9d2f11d9697f59b3814a9d3bb4b7a7c20b131df4bcef464037c0"}, + {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1e53262fd202e4b40b70c3aff944a8155059beedc8a89bba9dc1f9ef06a1b56"}, + {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d60ac9663f44168038586cab2157e122e46bdef09e9368b37f2d82d354c23f72"}, + {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:90751b8eed69435bac9ff4e3d2f6b3af1f57e37ecb0fbeee59c0174c9e2d41df"}, + {file = "aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fc353029f176fd2b3ec6cfc71be166aba1936fe5d73dd1992ce289ca6647a9aa"}, + {file = "aiohttp-3.13.3-cp314-cp314t-win32.whl", hash = "sha256:2e41b18a58da1e474a057b3d35248d8320029f61d70a37629535b16a0c8f3767"}, + {file = "aiohttp-3.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:44531a36aa2264a1860089ffd4dce7baf875ee5a6079d5fb42e261c704ef7344"}, + {file = "aiohttp-3.13.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31a83ea4aead760dfcb6962efb1d861db48c34379f2ff72db9ddddd4cda9ea2e"}, + {file = "aiohttp-3.13.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:988a8c5e317544fdf0d39871559e67b6341065b87fceac641108c2096d5506b7"}, + {file = "aiohttp-3.13.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9b174f267b5cfb9a7dba9ee6859cecd234e9a681841eb85068059bc867fb8f02"}, + {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:947c26539750deeaee933b000fb6517cc770bbd064bad6033f1cff4803881e43"}, + {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9ebf57d09e131f5323464bd347135a88622d1c0976e88ce15b670e7ad57e4bd6"}, + {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4ae5b5a0e1926e504c81c5b84353e7a5516d8778fbbff00429fe7b05bb25cbce"}, + {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ba0eea45eb5cc3172dbfc497c066f19c41bac70963ea1a67d51fc92e4cf9a80"}, + {file = "aiohttp-3.13.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bae5c2ed2eae26cc382020edad80d01f36cb8e746da40b292e68fec40421dc6a"}, + {file = "aiohttp-3.13.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a60e60746623925eab7d25823329941aee7242d559baa119ca2b253c88a7bd6"}, + {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e50a2e1404f063427c9d027378472316201a2290959a295169bcf25992d04558"}, + {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:9a9dc347e5a3dc7dfdbc1f82da0ef29e388ddb2ed281bfce9dd8248a313e62b7"}, + {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b46020d11d23fe16551466c77823df9cc2f2c1e63cc965daf67fa5eec6ca1877"}, + {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:69c56fbc1993fa17043e24a546959c0178fe2b5782405ad4559e6c13975c15e3"}, + {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b99281b0704c103d4e11e72a76f1b543d4946fea7dd10767e7e1b5f00d4e5704"}, + {file = "aiohttp-3.13.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:40c5e40ecc29ba010656c18052b877a1c28f84344825efa106705e835c28530f"}, + {file = "aiohttp-3.13.3-cp39-cp39-win32.whl", hash = "sha256:56339a36b9f1fc708260c76c87e593e2afb30d26de9ae1eb445b5e051b98a7a1"}, + {file = "aiohttp-3.13.3-cp39-cp39-win_amd64.whl", hash = "sha256:c6b8568a3bb5819a0ad087f16d40e5a3fb6099f39ea1d5625a3edc1e923fc538"}, + {file = "aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88"}, ] [package.dependencies] @@ -210,7 +210,7 @@ propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "backports.zstd ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"", "brotlicffi ; platform_python_implementation != \"CPython\""] +speedups = ["Brotli (>=1.2) ; platform_python_implementation == \"CPython\"", "aiodns (>=3.3.0)", "backports.zstd ; platform_python_implementation == \"CPython\" and python_version < \"3.14\"", "brotlicffi (>=1.2) ; platform_python_implementation != \"CPython\""] [[package]] name = "aiohttp-asyncmdnsresolver" @@ -312,7 +312,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["dev"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -654,8 +654,6 @@ files = [ ] [package.dependencies] -bleak = {version = ">=1", markers = "python_version >= \"3.10\" and python_version < \"3.14\""} -bluetooth-adapters = {version = ">=0.15.2", markers = "python_version >= \"3.10\" and python_version < \"3.14\" and platform_system == \"Linux\""} dbus-fast = {version = ">=1.14.0", markers = "platform_system == \"Linux\""} [[package]] @@ -1134,7 +1132,7 @@ version = "8.3.0" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" -groups = ["main", "dev"] +groups = ["main"] files = [ {file = "click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc"}, {file = "click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4"}, @@ -1270,66 +1268,61 @@ files = [ [[package]] name = "cryptography" -version = "46.0.2" +version = "46.0.5" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = "!=3.9.0,!=3.9.1,>=3.8" groups = ["main", "dev"] files = [ - {file = "cryptography-46.0.2-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3e32ab7dd1b1ef67b9232c4cf5e2ee4cd517d4316ea910acaaa9c5712a1c663"}, - {file = "cryptography-46.0.2-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1fd1a69086926b623ef8126b4c33d5399ce9e2f3fac07c9c734c2a4ec38b6d02"}, - {file = "cryptography-46.0.2-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb7fb9cd44c2582aa5990cf61a4183e6f54eea3172e54963787ba47287edd135"}, - {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9066cfd7f146f291869a9898b01df1c9b0e314bfa182cef432043f13fc462c92"}, - {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:97e83bf4f2f2c084d8dd792d13841d0a9b241643151686010866bbd076b19659"}, - {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:4a766d2a5d8127364fd936572c6e6757682fc5dfcbdba1632d4554943199f2fa"}, - {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:fab8f805e9675e61ed8538f192aad70500fa6afb33a8803932999b1049363a08"}, - {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:1e3b6428a3d56043bff0bb85b41c535734204e599c1c0977e1d0f261b02f3ad5"}, - {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:1a88634851d9b8de8bb53726f4300ab191d3b2f42595e2581a54b26aba71b7cc"}, - {file = "cryptography-46.0.2-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:be939b99d4e091eec9a2bcf41aaf8f351f312cd19ff74b5c83480f08a8a43e0b"}, - {file = "cryptography-46.0.2-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f13b040649bc18e7eb37936009b24fd31ca095a5c647be8bb6aaf1761142bd1"}, - {file = "cryptography-46.0.2-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9bdc25e4e01b261a8fda4e98618f1c9515febcecebc9566ddf4a70c63967043b"}, - {file = "cryptography-46.0.2-cp311-abi3-win32.whl", hash = "sha256:8b9bf67b11ef9e28f4d78ff88b04ed0929fcd0e4f70bb0f704cfc32a5c6311ee"}, - {file = "cryptography-46.0.2-cp311-abi3-win_amd64.whl", hash = "sha256:758cfc7f4c38c5c5274b55a57ef1910107436f4ae842478c4989abbd24bd5acb"}, - {file = "cryptography-46.0.2-cp311-abi3-win_arm64.whl", hash = "sha256:218abd64a2e72f8472c2102febb596793347a3e65fafbb4ad50519969da44470"}, - {file = "cryptography-46.0.2-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:bda55e8dbe8533937956c996beaa20266a8eca3570402e52ae52ed60de1faca8"}, - {file = "cryptography-46.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7155c0b004e936d381b15425273aee1cebc94f879c0ce82b0d7fecbf755d53a"}, - {file = "cryptography-46.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a61c154cc5488272a6c4b86e8d5beff4639cdb173d75325ce464d723cda0052b"}, - {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:9ec3f2e2173f36a9679d3b06d3d01121ab9b57c979de1e6a244b98d51fea1b20"}, - {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2fafb6aa24e702bbf74de4cb23bfa2c3beb7ab7683a299062b69724c92e0fa73"}, - {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:0c7ffe8c9b1fcbb07a26d7c9fa5e857c2fe80d72d7b9e0353dcf1d2180ae60ee"}, - {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:5840f05518caa86b09d23f8b9405a7b6d5400085aa14a72a98fdf5cf1568c0d2"}, - {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:27c53b4f6a682a1b645fbf1cd5058c72cf2f5aeba7d74314c36838c7cbc06e0f"}, - {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:512c0250065e0a6b286b2db4bbcc2e67d810acd53eb81733e71314340366279e"}, - {file = "cryptography-46.0.2-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:07c0eb6657c0e9cca5891f4e35081dbf985c8131825e21d99b4f440a8f496f36"}, - {file = "cryptography-46.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:48b983089378f50cba258f7f7aa28198c3f6e13e607eaf10472c26320332ca9a"}, - {file = "cryptography-46.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e6f6775eaaa08c0eec73e301f7592f4367ccde5e4e4df8e58320f2ebf161ea2c"}, - {file = "cryptography-46.0.2-cp314-cp314t-win32.whl", hash = "sha256:e8633996579961f9b5a3008683344c2558d38420029d3c0bc7ff77c17949a4e1"}, - {file = "cryptography-46.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:48c01988ecbb32979bb98731f5c2b2f79042a6c58cc9a319c8c2f9987c7f68f9"}, - {file = "cryptography-46.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:8e2ad4d1a5899b7caa3a450e33ee2734be7cc0689010964703a7c4bcc8dd4fd0"}, - {file = "cryptography-46.0.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a08e7401a94c002e79dc3bc5231b6558cd4b2280ee525c4673f650a37e2c7685"}, - {file = "cryptography-46.0.2-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d30bc11d35743bf4ddf76674a0a369ec8a21f87aaa09b0661b04c5f6c46e8d7b"}, - {file = "cryptography-46.0.2-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bca3f0ce67e5a2a2cf524e86f44697c4323a86e0fd7ba857de1c30d52c11ede1"}, - {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ff798ad7a957a5021dcbab78dfff681f0cf15744d0e6af62bd6746984d9c9e9c"}, - {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:cb5e8daac840e8879407acbe689a174f5ebaf344a062f8918e526824eb5d97af"}, - {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:3f37aa12b2d91e157827d90ce78f6180f0c02319468a0aea86ab5a9566da644b"}, - {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:5e38f203160a48b93010b07493c15f2babb4e0f2319bbd001885adb3f3696d21"}, - {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d19f5f48883752b5ab34cff9e2f7e4a7f216296f33714e77d1beb03d108632b6"}, - {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:04911b149eae142ccd8c9a68892a70c21613864afb47aba92d8c7ed9cc001023"}, - {file = "cryptography-46.0.2-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:8b16c1ede6a937c291d41176934268e4ccac2c6521c69d3f5961c5a1e11e039e"}, - {file = "cryptography-46.0.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:747b6f4a4a23d5a215aadd1d0b12233b4119c4313df83ab4137631d43672cc90"}, - {file = "cryptography-46.0.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6b275e398ab3a7905e168c036aad54b5969d63d3d9099a0a66cc147a3cc983be"}, - {file = "cryptography-46.0.2-cp38-abi3-win32.whl", hash = "sha256:0b507c8e033307e37af61cb9f7159b416173bdf5b41d11c4df2e499a1d8e007c"}, - {file = "cryptography-46.0.2-cp38-abi3-win_amd64.whl", hash = "sha256:f9b2dc7668418fb6f221e4bf701f716e05e8eadb4f1988a2487b11aedf8abe62"}, - {file = "cryptography-46.0.2-cp38-abi3-win_arm64.whl", hash = "sha256:91447f2b17e83c9e0c89f133119d83f94ce6e0fb55dd47da0a959316e6e9cfa1"}, - {file = "cryptography-46.0.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f25a41f5b34b371a06dad3f01799706631331adc7d6c05253f5bca22068c7a34"}, - {file = "cryptography-46.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e12b61e0b86611e3f4c1756686d9086c1d36e6fd15326f5658112ad1f1cc8807"}, - {file = "cryptography-46.0.2-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1d3b3edd145953832e09607986f2bd86f85d1dc9c48ced41808b18009d9f30e5"}, - {file = "cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:fe245cf4a73c20592f0f48da39748b3513db114465be78f0a36da847221bd1b4"}, - {file = "cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2b9cad9cf71d0c45566624ff76654e9bae5f8a25970c250a26ccfc73f8553e2d"}, - {file = "cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:9bd26f2f75a925fdf5e0a446c0de2714f17819bf560b44b7480e4dd632ad6c46"}, - {file = "cryptography-46.0.2-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:7282d8f092b5be7172d6472f29b0631f39f18512a3642aefe52c3c0e0ccfad5a"}, - {file = "cryptography-46.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c4b93af7920cdf80f71650769464ccf1fb49a4b56ae0024173c24c48eb6b1612"}, - {file = "cryptography-46.0.2.tar.gz", hash = "sha256:21b6fc8c71a3f9a604f028a329e5560009cc4a3a828bfea5fcba8eb7647d88fe"}, + {file = "cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0"}, + {file = "cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731"}, + {file = "cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82"}, + {file = "cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1"}, + {file = "cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48"}, + {file = "cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4"}, + {file = "cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0"}, + {file = "cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663"}, + {file = "cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826"}, + {file = "cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d"}, + {file = "cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a"}, + {file = "cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4"}, + {file = "cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d"}, + {file = "cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c"}, + {file = "cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4"}, + {file = "cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9"}, + {file = "cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72"}, + {file = "cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595"}, + {file = "cryptography-46.0.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:3b4995dc971c9fb83c25aa44cf45f02ba86f71ee600d81091c2f0cbae116b06c"}, + {file = "cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bc84e875994c3b445871ea7181d424588171efec3e185dced958dad9e001950a"}, + {file = "cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:2ae6971afd6246710480e3f15824ed3029a60fc16991db250034efd0b9fb4356"}, + {file = "cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:d861ee9e76ace6cf36a6a89b959ec08e7bc2493ee39d07ffe5acb23ef46d27da"}, + {file = "cryptography-46.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:2b7a67c9cd56372f3249b39699f2ad479f6991e62ea15800973b956f4b73e257"}, + {file = "cryptography-46.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8456928655f856c6e1533ff59d5be76578a7157224dbd9ce6872f25055ab9ab7"}, + {file = "cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d"}, ] [package.dependencies] @@ -1342,7 +1335,7 @@ nox = ["nox[uv] (>=2024.4.15)"] pep8test = ["check-sdist", "click (>=8.0.1)", "mypy (>=1.14)", "ruff (>=0.11.11)"] sdist = ["build (>=1.0.0)"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi (>=2024)", "cryptography-vectors (==46.0.2)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] +test = ["certifi (>=2024)", "cryptography-vectors (==46.0.5)", "pretend (>=0.7)", "pytest (>=7.4.0)", "pytest-benchmark (>=4.0)", "pytest-cov (>=2.10.1)", "pytest-xdist (>=3.5.0)"] test-randomorder = ["pytest-randomly"] [[package]] @@ -1429,18 +1422,6 @@ files = [ {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, ] -[[package]] -name = "distro" -version = "1.9.0" -description = "Distro - an OS platform information API" -optional = false -python-versions = ">=3.6" -groups = ["main", "dev"] -files = [ - {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, - {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, -] - [[package]] name = "envs" version = "1.4" @@ -1471,101 +1452,13 @@ files = [ [package.extras] testing = ["hatch", "pre-commit", "pytest", "tox"] -[[package]] -name = "fastuuid" -version = "0.14.0" -description = "Python bindings to Rust's UUID library." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "fastuuid-0.14.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:6e6243d40f6c793c3e2ee14c13769e341b90be5ef0c23c82fa6515a96145181a"}, - {file = "fastuuid-0.14.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:13ec4f2c3b04271f62be2e1ce7e95ad2dd1cf97e94503a3760db739afbd48f00"}, - {file = "fastuuid-0.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b2fdd48b5e4236df145a149d7125badb28e0a383372add3fbaac9a6b7a394470"}, - {file = "fastuuid-0.14.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f74631b8322d2780ebcf2d2d75d58045c3e9378625ec51865fe0b5620800c39d"}, - {file = "fastuuid-0.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83cffc144dc93eb604b87b179837f2ce2af44871a7b323f2bfed40e8acb40ba8"}, - {file = "fastuuid-0.14.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a771f135ab4523eb786e95493803942a5d1fc1610915f131b363f55af53b219"}, - {file = "fastuuid-0.14.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4edc56b877d960b4eda2c4232f953a61490c3134da94f3c28af129fb9c62a4f6"}, - {file = "fastuuid-0.14.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:bcc96ee819c282e7c09b2eed2b9bd13084e3b749fdb2faf58c318d498df2efbe"}, - {file = "fastuuid-0.14.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7a3c0bca61eacc1843ea97b288d6789fbad7400d16db24e36a66c28c268cfe3d"}, - {file = "fastuuid-0.14.0-cp310-cp310-win32.whl", hash = "sha256:7f2f3efade4937fae4e77efae1af571902263de7b78a0aee1a1653795a093b2a"}, - {file = "fastuuid-0.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:ae64ba730d179f439b0736208b4c279b8bc9c089b102aec23f86512ea458c8a4"}, - {file = "fastuuid-0.14.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:73946cb950c8caf65127d4e9a325e2b6be0442a224fd51ba3b6ac44e1912ce34"}, - {file = "fastuuid-0.14.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:12ac85024637586a5b69645e7ed986f7535106ed3013640a393a03e461740cb7"}, - {file = "fastuuid-0.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:05a8dde1f395e0c9b4be515b7a521403d1e8349443e7641761af07c7ad1624b1"}, - {file = "fastuuid-0.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09378a05020e3e4883dfdab438926f31fea15fd17604908f3d39cbeb22a0b4dc"}, - {file = "fastuuid-0.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbb0c4b15d66b435d2538f3827f05e44e2baafcc003dd7d8472dc67807ab8fd8"}, - {file = "fastuuid-0.14.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cd5a7f648d4365b41dbf0e38fe8da4884e57bed4e77c83598e076ac0c93995e7"}, - {file = "fastuuid-0.14.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c0a94245afae4d7af8c43b3159d5e3934c53f47140be0be624b96acd672ceb73"}, - {file = "fastuuid-0.14.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2b29e23c97e77c3a9514d70ce343571e469098ac7f5a269320a0f0b3e193ab36"}, - {file = "fastuuid-0.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1e690d48f923c253f28151b3a6b4e335f2b06bf669c68a02665bc150b7839e94"}, - {file = "fastuuid-0.14.0-cp311-cp311-win32.whl", hash = "sha256:a6f46790d59ab38c6aa0e35c681c0484b50dc0acf9e2679c005d61e019313c24"}, - {file = "fastuuid-0.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:e150eab56c95dc9e3fefc234a0eedb342fac433dacc273cd4d150a5b0871e1fa"}, - {file = "fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77e94728324b63660ebf8adb27055e92d2e4611645bf12ed9d88d30486471d0a"}, - {file = "fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:caa1f14d2102cb8d353096bc6ef6c13b2c81f347e6ab9d6fbd48b9dea41c153d"}, - {file = "fastuuid-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d23ef06f9e67163be38cece704170486715b177f6baae338110983f99a72c070"}, - {file = "fastuuid-0.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c9ec605ace243b6dbe3bd27ebdd5d33b00d8d1d3f580b39fdd15cd96fd71796"}, - {file = "fastuuid-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:808527f2407f58a76c916d6aa15d58692a4a019fdf8d4c32ac7ff303b7d7af09"}, - {file = "fastuuid-0.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fb3c0d7fef6674bbeacdd6dbd386924a7b60b26de849266d1ff6602937675c8"}, - {file = "fastuuid-0.14.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab3f5d36e4393e628a4df337c2c039069344db5f4b9d2a3c9cea48284f1dd741"}, - {file = "fastuuid-0.14.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b9a0ca4f03b7e0b01425281ffd44e99d360e15c895f1907ca105854ed85e2057"}, - {file = "fastuuid-0.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3acdf655684cc09e60fb7e4cf524e8f42ea760031945aa8086c7eae2eeeabeb8"}, - {file = "fastuuid-0.14.0-cp312-cp312-win32.whl", hash = "sha256:9579618be6280700ae36ac42c3efd157049fe4dd40ca49b021280481c78c3176"}, - {file = "fastuuid-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:d9e4332dc4ba054434a9594cbfaf7823b57993d7d8e7267831c3e059857cf397"}, - {file = "fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77a09cb7427e7af74c594e409f7731a0cf887221de2f698e1ca0ebf0f3139021"}, - {file = "fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:9bd57289daf7b153bfa3e8013446aa144ce5e8c825e9e366d455155ede5ea2dc"}, - {file = "fastuuid-0.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ac60fc860cdf3c3f327374db87ab8e064c86566ca8c49d2e30df15eda1b0c2d5"}, - {file = "fastuuid-0.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab32f74bd56565b186f036e33129da77db8be09178cd2f5206a5d4035fb2a23f"}, - {file = "fastuuid-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e678459cf4addaedd9936bbb038e35b3f6b2061330fd8f2f6a1d80414c0f87"}, - {file = "fastuuid-0.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e3cc56742f76cd25ecb98e4b82a25f978ccffba02e4bdce8aba857b6d85d87b"}, - {file = "fastuuid-0.14.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cb9a030f609194b679e1660f7e32733b7a0f332d519c5d5a6a0a580991290022"}, - {file = "fastuuid-0.14.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:09098762aad4f8da3a888eb9ae01c84430c907a297b97166b8abc07b640f2995"}, - {file = "fastuuid-0.14.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1383fff584fa249b16329a059c68ad45d030d5a4b70fb7c73a08d98fd53bcdab"}, - {file = "fastuuid-0.14.0-cp313-cp313-win32.whl", hash = "sha256:a0809f8cc5731c066c909047f9a314d5f536c871a7a22e815cc4967c110ac9ad"}, - {file = "fastuuid-0.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:0df14e92e7ad3276327631c9e7cec09e32572ce82089c55cb1bb8df71cf394ed"}, - {file = "fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:b852a870a61cfc26c884af205d502881a2e59cc07076b60ab4a951cc0c94d1ad"}, - {file = "fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c7502d6f54cd08024c3ea9b3514e2d6f190feb2f46e6dbcd3747882264bb5f7b"}, - {file = "fastuuid-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ca61b592120cf314cfd66e662a5b54a578c5a15b26305e1b8b618a6f22df714"}, - {file = "fastuuid-0.14.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa75b6657ec129d0abded3bec745e6f7ab642e6dba3a5272a68247e85f5f316f"}, - {file = "fastuuid-0.14.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8a0dfea3972200f72d4c7df02c8ac70bad1bb4c58d7e0ec1e6f341679073a7f"}, - {file = "fastuuid-0.14.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1bf539a7a95f35b419f9ad105d5a8a35036df35fdafae48fb2fd2e5f318f0d75"}, - {file = "fastuuid-0.14.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:9a133bf9cc78fdbd1179cb58a59ad0100aa32d8675508150f3658814aeefeaa4"}, - {file = "fastuuid-0.14.0-cp314-cp314-musllinux_1_1_i686.whl", hash = "sha256:f54d5b36c56a2d5e1a31e73b950b28a0d83eb0c37b91d10408875a5a29494bad"}, - {file = "fastuuid-0.14.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:ec27778c6ca3393ef662e2762dba8af13f4ec1aaa32d08d77f71f2a70ae9feb8"}, - {file = "fastuuid-0.14.0-cp314-cp314-win32.whl", hash = "sha256:e23fc6a83f112de4be0cc1990e5b127c27663ae43f866353166f87df58e73d06"}, - {file = "fastuuid-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:df61342889d0f5e7a32f7284e55ef95103f2110fee433c2ae7c2c0956d76ac8a"}, - {file = "fastuuid-0.14.0-cp38-cp38-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:47c821f2dfe95909ead0085d4cb18d5149bca704a2b03e03fb3f81a5202d8cea"}, - {file = "fastuuid-0.14.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:3964bab460c528692c70ab6b2e469dd7a7b152fbe8c18616c58d34c93a6cf8d4"}, - {file = "fastuuid-0.14.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c501561e025b7aea3508719c5801c360c711d5218fc4ad5d77bf1c37c1a75779"}, - {file = "fastuuid-0.14.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dce5d0756f046fa792a40763f36accd7e466525c5710d2195a038f93ff96346"}, - {file = "fastuuid-0.14.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:193ca10ff553cf3cc461572da83b5780fc0e3eea28659c16f89ae5202f3958d4"}, - {file = "fastuuid-0.14.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0737606764b29785566f968bd8005eace73d3666bd0862f33a760796e26d1ede"}, - {file = "fastuuid-0.14.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0976c0dff7e222513d206e06341503f07423aceb1db0b83ff6851c008ceee06"}, - {file = "fastuuid-0.14.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6fbc49a86173e7f074b1a9ec8cf12ca0d54d8070a85a06ebf0e76c309b84f0d0"}, - {file = "fastuuid-0.14.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:de01280eabcd82f7542828ecd67ebf1551d37203ecdfd7ab1f2e534edb78d505"}, - {file = "fastuuid-0.14.0-cp38-cp38-win32.whl", hash = "sha256:af5967c666b7d6a377098849b07f83462c4fedbafcf8eb8bc8ff05dcbe8aa209"}, - {file = "fastuuid-0.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3091e63acf42f56a6f74dc65cfdb6f99bfc79b5913c8a9ac498eb7ca09770a8"}, - {file = "fastuuid-0.14.0-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2ec3d94e13712a133137b2805073b65ecef4a47217d5bac15d8ac62376cefdb4"}, - {file = "fastuuid-0.14.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:139d7ff12bb400b4a0c76be64c28cbe2e2edf60b09826cbfd85f33ed3d0bbe8b"}, - {file = "fastuuid-0.14.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d55b7e96531216fc4f071909e33e35e5bfa47962ae67d9e84b00a04d6e8b7173"}, - {file = "fastuuid-0.14.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0eb25f0fd935e376ac4334927a59e7c823b36062080e2e13acbaf2af15db836"}, - {file = "fastuuid-0.14.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:089c18018fdbdda88a6dafd7d139f8703a1e7c799618e33ea25eb52503d28a11"}, - {file = "fastuuid-0.14.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fc37479517d4d70c08696960fad85494a8a7a0af4e93e9a00af04d74c59f9e3"}, - {file = "fastuuid-0.14.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:73657c9f778aba530bc96a943d30e1a7c80edb8278df77894fe9457540df4f85"}, - {file = "fastuuid-0.14.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d31f8c257046b5617fc6af9c69be066d2412bdef1edaa4bdf6a214cf57806105"}, - {file = "fastuuid-0.14.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5816d41f81782b209843e52fdef757a361b448d782452d96abedc53d545da722"}, - {file = "fastuuid-0.14.0-cp39-cp39-win32.whl", hash = "sha256:448aa6833f7a84bfe37dd47e33df83250f404d591eb83527fa2cac8d1e57d7f3"}, - {file = "fastuuid-0.14.0-cp39-cp39-win_amd64.whl", hash = "sha256:84b0779c5abbdec2a9511d5ffbfcd2e53079bf889824b32be170c0d8ef5fc74c"}, - {file = "fastuuid-0.14.0.tar.gz", hash = "sha256:178947fc2f995b38497a74172adee64fdeb8b7ec18f2a5934d037641ba265d26"}, -] - [[package]] name = "filelock" version = "3.19.1" description = "A platform independent file lock." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["dev"] files = [ {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, @@ -1772,134 +1665,6 @@ files = [ {file = "frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f"}, ] -[[package]] -name = "fsspec" -version = "2025.12.0" -description = "File-system specification" -optional = false -python-versions = ">=3.10" -groups = ["main", "dev"] -files = [ - {file = "fsspec-2025.12.0-py3-none-any.whl", hash = "sha256:8bf1fe301b7d8acfa6e8571e3b1c3d158f909666642431cc78a1b7b4dbc5ec5b"}, - {file = "fsspec-2025.12.0.tar.gz", hash = "sha256:c505de011584597b1060ff778bb664c1bc022e87921b0e4f10cc9c44f9635973"}, -] - -[package.extras] -abfs = ["adlfs"] -adl = ["adlfs"] -arrow = ["pyarrow (>=1)"] -dask = ["dask", "distributed"] -dev = ["pre-commit", "ruff (>=0.5)"] -doc = ["numpydoc", "sphinx", "sphinx-design", "sphinx-rtd-theme", "yarl"] -dropbox = ["dropbox", "dropboxdrivefs", "requests"] -full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] -fuse = ["fusepy"] -gcs = ["gcsfs"] -git = ["pygit2"] -github = ["requests"] -gs = ["gcsfs"] -gui = ["panel"] -hdfs = ["pyarrow (>=1)"] -http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"] -libarchive = ["libarchive-c"] -oci = ["ocifs"] -s3 = ["s3fs"] -sftp = ["paramiko"] -smb = ["smbprotocol"] -ssh = ["paramiko"] -test = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "numpy", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "requests"] -test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask[dataframe,test]", "moto[server] (>4,<5)", "pytest-timeout", "xarray"] -test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard ; python_version < \"3.14\""] -tqdm = ["tqdm"] - -[[package]] -name = "go2rtc-client" -version = "0.3.0" -description = "Python client for go2rtc" -optional = false -python-versions = ">=3.12.0" -groups = ["dev"] -files = [ - {file = "go2rtc_client-0.3.0-py3-none-any.whl", hash = "sha256:6e2b4bf9a386f2410770019d8b4e98cb2964f1d8c01b3d3c2598da4259f5d6d1"}, - {file = "go2rtc_client-0.3.0.tar.gz", hash = "sha256:6609c7081b9858cc8cefeffb2202c48536904afed80240ed13e913cc03882a95"}, -] - -[package.dependencies] -aiohttp = ">=3.10,<4.0" -awesomeversion = ">=24.6" -mashumaro = ">=3.13,<4.0" -orjson = ">=3.10,<4.0" -webrtc-models = ">=0.1,<1.0" - -[[package]] -name = "greenlet" -version = "3.2.4" -description = "Lightweight in-process concurrent programming" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -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\"" -files = [ - {file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d"}, - {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5"}, - {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f"}, - {file = "greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c"}, - {file = "greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8"}, - {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52"}, - {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa"}, - {file = "greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9"}, - {file = "greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0"}, - {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0"}, - {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f"}, - {file = "greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02"}, - {file = "greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671"}, - {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b"}, - {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae"}, - {file = "greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b"}, - {file = "greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337"}, - {file = "greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01"}, - {file = "greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df"}, - {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594"}, - {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98"}, - {file = "greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b"}, - {file = "greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb"}, - {file = "greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d"}, -] - -[package.extras] -docs = ["Sphinx", "furo"] -test = ["objgraph", "psutil", "setuptools"] - [[package]] name = "grpcio" version = "1.76.0" @@ -2059,18 +1824,18 @@ dbus-fast = {version = ">=2.30.2", markers = "platform_system == \"Linux\""} [[package]] name = "hass-nabucasa" -version = "1.7.0" +version = "1.12.0" description = "Home Assistant cloud integration by Nabu Casa, Inc." optional = false python-versions = ">=3.13" groups = ["main", "dev"] files = [ - {file = "hass_nabucasa-1.7.0-py3-none-any.whl", hash = "sha256:2256974ce3f9b4f170ed19e902427557d08ea2ac2b88c5feafd6a572d3e552ed"}, - {file = "hass_nabucasa-1.7.0.tar.gz", hash = "sha256:a6d25a02a538e316625ea48c44e70def10687def7b05e08da91da72004dd3ea4"}, + {file = "hass_nabucasa-1.12.0-py3-none-any.whl", hash = "sha256:90debd3efa2bdf6bca03e20f1a61e15441b260661ed17106dca6141b005ef788"}, + {file = "hass_nabucasa-1.12.0.tar.gz", hash = "sha256:06bc4ebe89ffd08b744aa6540a2ebc44a82f60e2e74645e3b7498385c88d722c"}, ] [package.dependencies] -acme = "5.1.0" +acme = "5.2.2" aiohttp = ">=3.6.1" async_timeout = ">=4" atomicwrites-homeassistant = "1.4.1" @@ -2078,8 +1843,8 @@ attrs = ">=19.3" ciso8601 = ">=2.3.0" cryptography = ">=42.0.0" grpcio = ">=1.75.1,<2" +icmplib = ">=3,<4" josepy = ">=2,<3" -litellm = ">=1.80.0,<2" pycognito = "2024.5.1" PyJWT = ">=2.8.0" sentence-stream = ">=1.2.0,<2" @@ -2089,43 +1854,7 @@ webrtc-models = "<1.0.0" yarl = ">=1.20,<2" [package.extras] -test = ["codespell (==2.4.1)", "freezegun (==1.5.5)", "mypy (==1.18.2)", "pre-commit (==4.5.0)", "pre-commit-hooks (==6.0.0)", "pylint (==4.0.3)", "pytest (==9.0.1)", "pytest-aiohttp (==1.1.0)", "pytest-socket (==0.7.0)", "pytest-timeout (==2.4.0)", "ruff (==0.14.6)", "syrupy (==5.0.0)", "tomli (==2.3.0)", "types_atomicwrites (==1.4.5.1)", "types_pyOpenSSL (==24.1.0.20240722)", "xmltodict (==1.0.2)"] - -[[package]] -name = "hf-xet" -version = "1.2.0" -description = "Fast transfer of large files with the Hugging Face Hub." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\"" -files = [ - {file = "hf_xet-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:ceeefcd1b7aed4956ae8499e2199607765fbd1c60510752003b6cc0b8413b649"}, - {file = "hf_xet-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b70218dd548e9840224df5638fdc94bd033552963cfa97f9170829381179c813"}, - {file = "hf_xet-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d40b18769bb9a8bc82a9ede575ce1a44c75eb80e7375a01d76259089529b5dc"}, - {file = "hf_xet-1.2.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cd3a6027d59cfb60177c12d6424e31f4b5ff13d8e3a1247b3a584bf8977e6df5"}, - {file = "hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6de1fc44f58f6dd937956c8d304d8c2dea264c80680bcfa61ca4a15e7b76780f"}, - {file = "hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f182f264ed2acd566c514e45da9f2119110e48a87a327ca271027904c70c5832"}, - {file = "hf_xet-1.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:293a7a3787e5c95d7be1857358a9130694a9c6021de3f27fa233f37267174382"}, - {file = "hf_xet-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:10bfab528b968c70e062607f663e21e34e2bba349e8038db546646875495179e"}, - {file = "hf_xet-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a212e842647b02eb6a911187dc878e79c4aa0aa397e88dd3b26761676e8c1f8"}, - {file = "hf_xet-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30e06daccb3a7d4c065f34fc26c14c74f4653069bb2b194e7f18f17cbe9939c0"}, - {file = "hf_xet-1.2.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:29c8fc913a529ec0a91867ce3d119ac1aac966e098cf49501800c870328cc090"}, - {file = "hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e159cbfcfbb29f920db2c09ed8b660eb894640d284f102ada929b6e3dc410a"}, - {file = "hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c91d5ae931510107f148874e9e2de8a16052b6f1b3ca3c1b12f15ccb491390f"}, - {file = "hf_xet-1.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:210d577732b519ac6ede149d2f2f34049d44e8622bf14eb3d63bbcd2d4b332dc"}, - {file = "hf_xet-1.2.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:46740d4ac024a7ca9b22bebf77460ff43332868b661186a8e46c227fdae01848"}, - {file = "hf_xet-1.2.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:27df617a076420d8845bea087f59303da8be17ed7ec0cd7ee3b9b9f579dff0e4"}, - {file = "hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3651fd5bfe0281951b988c0facbe726aa5e347b103a675f49a3fa8144c7968fd"}, - {file = "hf_xet-1.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d06fa97c8562fb3ee7a378dd9b51e343bc5bc8190254202c9771029152f5e08c"}, - {file = "hf_xet-1.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4c1428c9ae73ec0939410ec73023c4f842927f39db09b063b9482dac5a3bb737"}, - {file = "hf_xet-1.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a55558084c16b09b5ed32ab9ed38421e2d87cf3f1f89815764d1177081b99865"}, - {file = "hf_xet-1.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:e6584a52253f72c9f52f9e549d5895ca7a471608495c4ecaa6cc73dba2b24d69"}, - {file = "hf_xet-1.2.0.tar.gz", hash = "sha256:a8c27070ca547293b6890c4bf389f713f80e8c478631432962bb7f4bc0bd7d7f"}, -] - -[package.extras] -tests = ["pytest"] +test = ["codespell (==2.4.1)", "freezegun (==1.5.5)", "mypy (==1.19.1)", "pre-commit (==4.5.1)", "pre-commit-hooks (==6.0.0)", "pylint (==4.0.4)", "pytest (==9.0.2)", "pytest-aiohttp (==1.1.0)", "pytest-socket (==0.7.0)", "pytest-timeout (==2.4.0)", "ruff (==0.14.14)", "syrupy (==5.1.0)", "tomli (==2.4.0)", "types_atomicwrites (==1.4.5.1)", "types_pyOpenSSL (==24.1.0.20240722)", "xmltodict (==1.0.2)"] [[package]] name = "home-assistant-bluetooth" @@ -2144,20 +1873,20 @@ habluetooth = ">=3.0" [[package]] name = "homeassistant" -version = "2025.12.4" +version = "2026.2.2" description = "Open-source home automation platform running on Python 3." optional = false python-versions = ">=3.13.2" groups = ["main", "dev"] files = [ - {file = "homeassistant-2025.12.4-py3-none-any.whl", hash = "sha256:b42f629c10a5d28062ef9527810139677b41daee7e6b1c1af20c0bb185e529c4"}, - {file = "homeassistant-2025.12.4.tar.gz", hash = "sha256:c157b9fecca3f3bf01de8ed05a0436c0e12c6ddcaf1560ff54c829a24d52c0e4"}, + {file = "homeassistant-2026.2.2-py3-none-any.whl", hash = "sha256:ef3e6a4e1cf96f4ad36062163b24b08488c3fe32f6115c2e02a0cc30ba65b30a"}, + {file = "homeassistant-2026.2.2.tar.gz", hash = "sha256:418a5f375bda07d9136ef256a7b1a8fc7c3b891f00e63da59c829cafba7d32ce"}, ] [package.dependencies] -aiodns = "3.6.1" +aiodns = "4.0.0" aiohasupervisor = "0.3.3" -aiohttp = "3.13.2" +aiohttp = "3.13.3" aiohttp-asyncmdnsresolver = "0.1.1" aiohttp_cors = "0.8.1" aiohttp-fast-zlib = "0.3.0" @@ -2173,15 +1902,15 @@ bcrypt = "5.0.0" certifi = ">=2021.5.30" ciso8601 = "2.3.3" cronsim = "2.7" -cryptography = "46.0.2" +cryptography = "46.0.5" fnv-hash-fast = "1.6.0" -hass-nabucasa = "1.7.0" +hass-nabucasa = "1.12.0" home-assistant-bluetooth = "1.13.1" httpx = "0.28.1" ifaddr = "0.2.0" Jinja2 = "3.1.6" lru-dict = "1.3.0" -orjson = "3.11.3" +orjson = "3.11.5" packaging = ">=23.1" Pillow = "12.0.0" propcache = "0.4.1" @@ -2198,9 +1927,9 @@ standard-telnetlib = "3.13.0" typing-extensions = ">=4.15.0,<5.0" ulid-transform = "1.5.2" urllib3 = ">=2.0" -uv = "0.9.6" +uv = "0.9.26" voluptuous = "0.15.2" -voluptuous-openapi = "0.1.0" +voluptuous-openapi = "0.2.0" voluptuous-serialize = "2.7.0" webrtc-models = "0.3.0" yarl = "1.22.0" @@ -2208,18 +1937,18 @@ zeroconf = "0.148.0" [[package]] name = "homeassistant-stubs" -version = "2025.12.4" +version = "2026.2.2" description = "PEP 484 typing stubs for Home Assistant Core" optional = false python-versions = ">=3.13.2" groups = ["dev"] files = [ - {file = "homeassistant_stubs-2025.12.4-py3-none-any.whl", hash = "sha256:d7402ae229320d0fb89375962504e91ffbf4f07f7c8b12b1fa0d27c3121517df"}, - {file = "homeassistant_stubs-2025.12.4.tar.gz", hash = "sha256:be5f301b622ee03d19cc65163f4481bbe312826d19027417f80b8e1991a00aba"}, + {file = "homeassistant_stubs-2026.2.2-py3-none-any.whl", hash = "sha256:5cb40c3f07c1d102edbf2e8452f6ef279524482fc6efd6cbd716d01c47c83113"}, + {file = "homeassistant_stubs-2026.2.2.tar.gz", hash = "sha256:c660c84f8b81038f77dfb644e098d0d86901d19945364d927a4640414110ca1a"}, ] [package.dependencies] -homeassistant = "2025.12.4" +homeassistant = "2026.2.2" [[package]] name = "httpcore" @@ -2269,41 +1998,17 @@ socks = ["socksio (==1.*)"] zstd = ["zstandard (>=0.18.0)"] [[package]] -name = "huggingface-hub" -version = "1.2.2" -description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" +name = "icmplib" +version = "3.0.4" +description = "Easily forge ICMP packets and make your own ping and traceroute." optional = false -python-versions = ">=3.9.0" +python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "huggingface_hub-1.2.2-py3-none-any.whl", hash = "sha256:0f55d7d22058fbf8b29d8095aeee80a7b695aa764f906a21e886c1f87223718f"}, - {file = "huggingface_hub-1.2.2.tar.gz", hash = "sha256:b5b97bd37f4fe5b898a467373044649c94ee32006c032ce8fb835abe9d92ea28"}, + {file = "icmplib-3.0.4-py3-none-any.whl", hash = "sha256:336b75c6c23c5ce99ddec33f718fab09661f6ad698e35b6f1fc7cc0ecf809398"}, + {file = "icmplib-3.0.4.tar.gz", hash = "sha256:57868f2cdb011418c0e1d5586b16d1fabd206569fe9652654c27b6b2d6a316de"}, ] -[package.dependencies] -filelock = "*" -fsspec = ">=2023.5.0" -hf-xet = {version = ">=1.2.0,<2.0.0", markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\""} -httpx = ">=0.23.0,<1" -packaging = ">=20.9" -pyyaml = ">=5.1" -shellingham = "*" -tqdm = ">=4.42.1" -typer-slim = "*" -typing-extensions = ">=3.7.4.3" - -[package.extras] -all = ["Jinja2", "Pillow", "authlib (>=1.3.2)", "fastapi", "fastapi", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0)", "numpy", "pytest (>=8.4.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (<16.0)", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "ty", "types-PyYAML", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] -dev = ["Jinja2", "Pillow", "authlib (>=1.3.2)", "fastapi", "fastapi", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0)", "numpy", "pytest (>=8.4.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (<16.0)", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "ty", "types-PyYAML", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] -fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] -hf-xet = ["hf-xet (>=1.1.3,<2.0.0)"] -mcp = ["mcp (>=1.8.0)"] -oauth = ["authlib (>=1.3.2)", "fastapi", "httpx", "itsdangerous"] -quality = ["libcst (>=1.4.0)", "mypy (==1.15.0)", "ruff (>=0.9.0)", "ty"] -testing = ["Jinja2", "Pillow", "authlib (>=1.3.2)", "fastapi", "fastapi", "httpx", "itsdangerous", "jedi", "numpy", "pytest (>=8.4.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures (<16.0)", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] -torch = ["safetensors[torch]", "torch"] -typing = ["types-PyYAML", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] - [[package]] name = "identify" version = "2.6.14" @@ -2346,30 +2051,6 @@ files = [ {file = "ifaddr-0.2.0.tar.gz", hash = "sha256:cc0cbfcaabf765d44595825fb96a99bb12c79716b73b44330ea38ee2b0c4aed4"}, ] -[[package]] -name = "importlib-metadata" -version = "8.7.0" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, - {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, -] - -[package.dependencies] -zipp = ">=3.20" - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["pytest-mypy"] - [[package]] name = "iniconfig" version = "2.1.0" @@ -2416,118 +2097,6 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] -[[package]] -name = "jiter" -version = "0.12.0" -description = "Fast iterable JSON parser." -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "jiter-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:e7acbaba9703d5de82a2c98ae6a0f59ab9770ab5af5fa35e43a303aee962cf65"}, - {file = "jiter-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:364f1a7294c91281260364222f535bc427f56d4de1d8ffd718162d21fbbd602e"}, - {file = "jiter-0.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85ee4d25805d4fb23f0a5167a962ef8e002dbfb29c0989378488e32cf2744b62"}, - {file = "jiter-0.12.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:796f466b7942107eb889c08433b6e31b9a7ed31daceaecf8af1be26fb26c0ca8"}, - {file = "jiter-0.12.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35506cb71f47dba416694e67af996bbdefb8e3608f1f78799c2e1f9058b01ceb"}, - {file = "jiter-0.12.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:726c764a90c9218ec9e4f99a33d6bf5ec169163f2ca0fc21b654e88c2abc0abc"}, - {file = "jiter-0.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa47810c5565274810b726b0dc86d18dce5fd17b190ebdc3890851d7b2a0e74"}, - {file = "jiter-0.12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ec0259d3f26c62aed4d73b198c53e316ae11f0f69c8fbe6682c6dcfa0fcce2"}, - {file = "jiter-0.12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:79307d74ea83465b0152fa23e5e297149506435535282f979f18b9033c0bb025"}, - {file = "jiter-0.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cf6e6dd18927121fec86739f1a8906944703941d000f0639f3eb6281cc601dca"}, - {file = "jiter-0.12.0-cp310-cp310-win32.whl", hash = "sha256:b6ae2aec8217327d872cbfb2c1694489057b9433afce447955763e6ab015b4c4"}, - {file = "jiter-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:c7f49ce90a71e44f7e1aa9e7ec415b9686bbc6a5961e57eab511015e6759bc11"}, - {file = "jiter-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8f8a7e317190b2c2d60eb2e8aa835270b008139562d70fe732e1c0020ec53c9"}, - {file = "jiter-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2218228a077e784c6c8f1a8e5d6b8cb1dea62ce25811c356364848554b2056cd"}, - {file = "jiter-0.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9354ccaa2982bf2188fd5f57f79f800ef622ec67beb8329903abf6b10da7d423"}, - {file = "jiter-0.12.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2607185ea89b4af9a604d4c7ec40e45d3ad03ee66998b031134bc510232bb7"}, - {file = "jiter-0.12.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a585a5e42d25f2e71db5f10b171f5e5ea641d3aa44f7df745aa965606111cc2"}, - {file = "jiter-0.12.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd9e21d34edff5a663c631f850edcb786719c960ce887a5661e9c828a53a95d9"}, - {file = "jiter-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a612534770470686cd5431478dc5a1b660eceb410abade6b1b74e320ca98de6"}, - {file = "jiter-0.12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3985aea37d40a908f887b34d05111e0aae822943796ebf8338877fee2ab67725"}, - {file = "jiter-0.12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b1207af186495f48f72529f8d86671903c8c10127cac6381b11dddc4aaa52df6"}, - {file = "jiter-0.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef2fb241de583934c9915a33120ecc06d94aa3381a134570f59eed784e87001e"}, - {file = "jiter-0.12.0-cp311-cp311-win32.whl", hash = "sha256:453b6035672fecce8007465896a25b28a6b59cfe8fbc974b2563a92f5a92a67c"}, - {file = "jiter-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:ca264b9603973c2ad9435c71a8ec8b49f8f715ab5ba421c85a51cde9887e421f"}, - {file = "jiter-0.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:cb00ef392e7d684f2754598c02c409f376ddcef857aae796d559e6cacc2d78a5"}, - {file = "jiter-0.12.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:305e061fa82f4680607a775b2e8e0bcb071cd2205ac38e6ef48c8dd5ebe1cf37"}, - {file = "jiter-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c1860627048e302a528333c9307c818c547f214d8659b0705d2195e1a94b274"}, - {file = "jiter-0.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df37577a4f8408f7e0ec3205d2a8f87672af8f17008358063a4d6425b6081ce3"}, - {file = "jiter-0.12.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fdd787356c1c13a4f40b43c2156276ef7a71eb487d98472476476d803fb2cf"}, - {file = "jiter-0.12.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1eb5db8d9c65b112aacf14fcd0faae9913d07a8afea5ed06ccdd12b724e966a1"}, - {file = "jiter-0.12.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73c568cc27c473f82480abc15d1301adf333a7ea4f2e813d6a2c7d8b6ba8d0df"}, - {file = "jiter-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4321e8a3d868919bcb1abb1db550d41f2b5b326f72df29e53b2df8b006eb9403"}, - {file = "jiter-0.12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0a51bad79f8cc9cac2b4b705039f814049142e0050f30d91695a2d9a6611f126"}, - {file = "jiter-0.12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2a67b678f6a5f1dd6c36d642d7db83e456bc8b104788262aaefc11a22339f5a9"}, - {file = "jiter-0.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efe1a211fe1fd14762adea941e3cfd6c611a136e28da6c39272dbb7a1bbe6a86"}, - {file = "jiter-0.12.0-cp312-cp312-win32.whl", hash = "sha256:d779d97c834b4278276ec703dc3fc1735fca50af63eb7262f05bdb4e62203d44"}, - {file = "jiter-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e8269062060212b373316fe69236096aaf4c49022d267c6736eebd66bbbc60bb"}, - {file = "jiter-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:06cb970936c65de926d648af0ed3d21857f026b1cf5525cb2947aa5e01e05789"}, - {file = "jiter-0.12.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6cc49d5130a14b732e0612bc76ae8db3b49898732223ef8b7599aa8d9810683e"}, - {file = "jiter-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37f27a32ce36364d2fa4f7fdc507279db604d27d239ea2e044c8f148410defe1"}, - {file = "jiter-0.12.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbc0944aa3d4b4773e348cda635252824a78f4ba44328e042ef1ff3f6080d1cf"}, - {file = "jiter-0.12.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da25c62d4ee1ffbacb97fac6dfe4dcd6759ebdc9015991e92a6eae5816287f44"}, - {file = "jiter-0.12.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:048485c654b838140b007390b8182ba9774621103bd4d77c9c3f6f117474ba45"}, - {file = "jiter-0.12.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:635e737fbb7315bef0037c19b88b799143d2d7d3507e61a76751025226b3ac87"}, - {file = "jiter-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e017c417b1ebda911bd13b1e40612704b1f5420e30695112efdbed8a4b389ed"}, - {file = "jiter-0.12.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:89b0bfb8b2bf2351fba36bb211ef8bfceba73ef58e7f0c68fb67b5a2795ca2f9"}, - {file = "jiter-0.12.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:f5aa5427a629a824a543672778c9ce0c5e556550d1569bb6ea28a85015287626"}, - {file = "jiter-0.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed53b3d6acbcb0fd0b90f20c7cb3b24c357fe82a3518934d4edfa8c6898e498c"}, - {file = "jiter-0.12.0-cp313-cp313-win32.whl", hash = "sha256:4747de73d6b8c78f2e253a2787930f4fffc68da7fa319739f57437f95963c4de"}, - {file = "jiter-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:e25012eb0c456fcc13354255d0338cd5397cce26c77b2832b3c4e2e255ea5d9a"}, - {file = "jiter-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:c97b92c54fe6110138c872add030a1f99aea2401ddcdaa21edf74705a646dd60"}, - {file = "jiter-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:53839b35a38f56b8be26a7851a48b89bc47e5d88e900929df10ed93b95fea3d6"}, - {file = "jiter-0.12.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94f669548e55c91ab47fef8bddd9c954dab1938644e715ea49d7e117015110a4"}, - {file = "jiter-0.12.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:351d54f2b09a41600ffea43d081522d792e81dcfb915f6d2d242744c1cc48beb"}, - {file = "jiter-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2a5e90604620f94bf62264e7c2c038704d38217b7465b863896c6d7c902b06c7"}, - {file = "jiter-0.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:88ef757017e78d2860f96250f9393b7b577b06a956ad102c29c8237554380db3"}, - {file = "jiter-0.12.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c46d927acd09c67a9fb1416df45c5a04c27e83aae969267e98fba35b74e99525"}, - {file = "jiter-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:774ff60b27a84a85b27b88cd5583899c59940bcc126caca97eb2a9df6aa00c49"}, - {file = "jiter-0.12.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5433fab222fb072237df3f637d01b81f040a07dcac1cb4a5c75c7aa9ed0bef1"}, - {file = "jiter-0.12.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8c593c6e71c07866ec6bfb790e202a833eeec885022296aff6b9e0b92d6a70e"}, - {file = "jiter-0.12.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90d32894d4c6877a87ae00c6b915b609406819dce8bc0d4e962e4de2784e567e"}, - {file = "jiter-0.12.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:798e46eed9eb10c3adbbacbd3bdb5ecd4cf7064e453d00dbef08802dae6937ff"}, - {file = "jiter-0.12.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3f1368f0a6719ea80013a4eb90ba72e75d7ea67cfc7846db2ca504f3df0169a"}, - {file = "jiter-0.12.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65f04a9d0b4406f7e51279710b27484af411896246200e461d80d3ba0caa901a"}, - {file = "jiter-0.12.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:fd990541982a24281d12b67a335e44f117e4c6cbad3c3b75c7dea68bf4ce3a67"}, - {file = "jiter-0.12.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:b111b0e9152fa7df870ecaebb0bd30240d9f7fff1f2003bcb4ed0f519941820b"}, - {file = "jiter-0.12.0-cp314-cp314-win32.whl", hash = "sha256:a78befb9cc0a45b5a5a0d537b06f8544c2ebb60d19d02c41ff15da28a9e22d42"}, - {file = "jiter-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:e1fe01c082f6aafbe5c8faf0ff074f38dfb911d53f07ec333ca03f8f6226debf"}, - {file = "jiter-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:d72f3b5a432a4c546ea4bedc84cce0c3404874f1d1676260b9c7f048a9855451"}, - {file = "jiter-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e6ded41aeba3603f9728ed2b6196e4df875348ab97b28fc8afff115ed42ba7a7"}, - {file = "jiter-0.12.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a947920902420a6ada6ad51892082521978e9dd44a802663b001436e4b771684"}, - {file = "jiter-0.12.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:add5e227e0554d3a52cf390a7635edaffdf4f8fce4fdbcef3cc2055bb396a30c"}, - {file = "jiter-0.12.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f9b1cda8fcb736250d7e8711d4580ebf004a46771432be0ae4796944b5dfa5d"}, - {file = "jiter-0.12.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deeb12a2223fe0135c7ff1356a143d57f95bbf1f4a66584f1fc74df21d86b993"}, - {file = "jiter-0.12.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c596cc0f4cb574877550ce4ecd51f8037469146addd676d7c1a30ebe6391923f"}, - {file = "jiter-0.12.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ab4c823b216a4aeab3fdbf579c5843165756bd9ad87cc6b1c65919c4715f783"}, - {file = "jiter-0.12.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e427eee51149edf962203ff8db75a7514ab89be5cb623fb9cea1f20b54f1107b"}, - {file = "jiter-0.12.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:edb868841f84c111255ba5e80339d386d937ec1fdce419518ce1bd9370fac5b6"}, - {file = "jiter-0.12.0-cp314-cp314t-win32.whl", hash = "sha256:8bbcfe2791dfdb7c5e48baf646d37a6a3dcb5a97a032017741dea9f817dca183"}, - {file = "jiter-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2fa940963bf02e1d8226027ef461e36af472dea85d36054ff835aeed944dd873"}, - {file = "jiter-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:506c9708dd29b27288f9f8f1140c3cb0e3d8ddb045956d7757b1fa0e0f39a473"}, - {file = "jiter-0.12.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c9d28b218d5f9e5f69a0787a196322a5056540cb378cac8ff542b4fa7219966c"}, - {file = "jiter-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d0ee12028daf8cfcf880dd492349a122a64f42c059b6c62a2b0c96a83a8da820"}, - {file = "jiter-0.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b135ebe757a82d67ed2821526e72d0acf87dd61f6013e20d3c45b8048af927b"}, - {file = "jiter-0.12.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15d7fafb81af8a9e3039fc305529a61cd933eecee33b4251878a1c89859552a3"}, - {file = "jiter-0.12.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92d1f41211d8a8fe412faad962d424d334764c01dac6691c44691c2e4d3eedaf"}, - {file = "jiter-0.12.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a64a48d7c917b8f32f25c176df8749ecf08cec17c466114727efe7441e17f6d"}, - {file = "jiter-0.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:122046f3b3710b85de99d9aa2f3f0492a8233a2f54a64902b096efc27ea747b5"}, - {file = "jiter-0.12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:27ec39225e03c32c6b863ba879deb427882f243ae46f0d82d68b695fa5b48b40"}, - {file = "jiter-0.12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26b9e155ddc132225a39b1995b3b9f0fe0f79a6d5cbbeacf103271e7d309b404"}, - {file = "jiter-0.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ab05b7c58e29bb9e60b70c2e0094c98df79a1e42e397b9bb6eaa989b7a66dd0"}, - {file = "jiter-0.12.0-cp39-cp39-win32.whl", hash = "sha256:59f9f9df87ed499136db1c2b6c9efb902f964bed42a582ab7af413b6a293e7b0"}, - {file = "jiter-0.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:d3719596a1ebe7a48a498e8d5d0c4bf7553321d4c3eee1d620628d51351a3928"}, - {file = "jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:4739a4657179ebf08f85914ce50332495811004cc1747852e8b2041ed2aab9b8"}, - {file = "jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:41da8def934bf7bec16cb24bd33c0ca62126d2d45d81d17b864bd5ad721393c3"}, - {file = "jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c44ee814f499c082e69872d426b624987dbc5943ab06e9bbaa4f81989fdb79e"}, - {file = "jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd2097de91cf03eaa27b3cbdb969addf83f0179c6afc41bbc4513705e013c65d"}, - {file = "jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:e8547883d7b96ef2e5fe22b88f8a4c8725a56e7f4abafff20fd5272d634c7ecb"}, - {file = "jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:89163163c0934854a668ed783a2546a0617f71706a2551a4a0666d91ab365d6b"}, - {file = "jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d96b264ab7d34bbb2312dedc47ce07cd53f06835eacbc16dde3761f47c3a9e7f"}, - {file = "jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24e864cb30ab82311c6425655b0cdab0a98c5d973b065c66a3f020740c2324c"}, - {file = "jiter-0.12.0.tar.gz", hash = "sha256:64dfcd7d5c168b38d3f9f8bba7fc639edb3418abcc74f22fdbe6b8938293f30b"}, -] - [[package]] name = "jmespath" version = "1.0.1" @@ -2558,43 +2127,6 @@ cryptography = ">=1.5" [package.extras] docs = ["sphinx (>=4.3.0)", "sphinx-rtd-theme (>=1.0)"] -[[package]] -name = "jsonschema" -version = "4.25.1" -description = "An implementation of JSON Schema validation for Python" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63"}, - {file = "jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85"}, -] - -[package.dependencies] -attrs = ">=22.2.0" -jsonschema-specifications = ">=2023.03.6" -referencing = ">=0.28.4" -rpds-py = ">=0.7.1" - -[package.extras] -format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] -format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "rfc3987-syntax (>=1.1.0)", "uri-template", "webcolors (>=24.6.0)"] - -[[package]] -name = "jsonschema-specifications" -version = "2025.9.1" -description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe"}, - {file = "jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d"}, -] - -[package.dependencies] -referencing = ">=0.31.0" - [[package]] name = "license-expression" version = "30.4.3" @@ -2613,40 +2145,6 @@ files = [ [package.extras] dev = ["Sphinx (>=5.0.2)", "doc8 (>=0.11.2)", "pytest (>=7.0.1)", "pytest-xdist (>=2)", "ruff", "sphinx-autobuild", "sphinx-copybutton", "sphinx-reredirects (>=0.1.2)", "sphinx-rtd-dark-mode (>=1.3.0)", "sphinx-rtd-theme (>=1.0.0)", "sphinxcontrib-apidoc (>=0.4.0)", "twine"] -[[package]] -name = "litellm" -version = "1.80.5" -description = "Library to easily interface with LLM API providers" -optional = false -python-versions = "<4.0,>=3.9" -groups = ["main", "dev"] -files = [ - {file = "litellm-1.80.5-py3-none-any.whl", hash = "sha256:2ac5f4e88cd57ae056e00da8f872e1c2956653750929fba2fd9b007b400fdb77"}, - {file = "litellm-1.80.5.tar.gz", hash = "sha256:922791c264845d9ed59e540c8fa74a74d237c1b209568a05ffeacd8b51770deb"}, -] - -[package.dependencies] -aiohttp = ">=3.10" -click = "*" -fastuuid = ">=0.13.0" -httpx = ">=0.23.0" -importlib-metadata = ">=6.8.0" -jinja2 = ">=3.1.2,<4.0.0" -jsonschema = ">=4.22.0,<5.0.0" -openai = ">=2.8.0" -pydantic = ">=2.5.0,<3.0.0" -python-dotenv = ">=0.2.0" -tiktoken = ">=0.7.0" -tokenizers = "*" - -[package.extras] -caching = ["diskcache (>=5.6.1,<6.0.0)"] -extra-proxy = ["azure-identity (>=1.15.0,<2.0.0) ; python_version >= \"3.9\"", "azure-keyvault-secrets (>=4.8.0,<5.0.0)", "google-cloud-iam (>=2.19.1,<3.0.0)", "google-cloud-kms (>=2.21.3,<3.0.0)", "prisma (==0.11.0)", "redisvl (>=0.4.1,<0.5.0) ; python_version >= \"3.9\" and python_version < \"3.14\"", "resend (>=0.8.0)"] -mlflow = ["mlflow (>3.1.4) ; python_version >= \"3.10\""] -proxy = ["PyJWT (>=2.10.1,<3.0.0) ; python_version >= \"3.9\"", "apscheduler (>=3.10.4,<4.0.0)", "azure-identity (>=1.15.0,<2.0.0) ; python_version >= \"3.9\"", "azure-storage-blob (>=12.25.1,<13.0.0)", "backoff", "boto3 (==1.36.0)", "cryptography", "fastapi (>=0.120.1)", "fastapi-sso (>=0.16.0,<0.17.0)", "gunicorn (>=23.0.0,<24.0.0)", "litellm-enterprise (==0.1.22)", "litellm-proxy-extras (==0.4.6)", "mcp (>=1.21.2,<2.0.0) ; python_version >= \"3.10\"", "orjson (>=3.9.7,<4.0.0)", "polars (>=1.31.0,<2.0.0) ; python_version >= \"3.10\"", "pynacl (>=1.5.0,<2.0.0)", "python-multipart (>=0.0.18,<0.0.19)", "pyyaml (>=6.0.1,<7.0.0)", "rich (==13.7.1)", "rq", "soundfile (>=0.12.1,<0.13.0)", "uvicorn (>=0.31.1,<0.32.0)", "uvloop (>=0.21.0,<0.22.0) ; sys_platform != \"win32\"", "websockets (>=13.1.0,<14.0.0)"] -semantic-router = ["semantic-router (>=0.1.12) ; python_version >= \"3.9\" and python_version < \"3.14\""] -utils = ["numpydoc"] - [[package]] name = "lru-dict" version = "1.3.0" @@ -2856,18 +2354,18 @@ files = [ [[package]] name = "mashumaro" -version = "3.16" +version = "3.20" description = "Fast and well tested serialization library" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "mashumaro-3.16-py3-none-any.whl", hash = "sha256:d72782cdad5e164748ca883023bc5a214a80835cdca75826bf0bcbff827e0bd3"}, - {file = "mashumaro-3.16.tar.gz", hash = "sha256:3844137cf053bbac30c4cbd0ee9984e839a5731a0ef96fd3dd9388359af3f2e1"}, + {file = "mashumaro-3.20-py3-none-any.whl", hash = "sha256:648bc326f64c55447988eab67d6bfe3b7958c0961c83590709b1f950f88f4a3c"}, + {file = "mashumaro-3.20.tar.gz", hash = "sha256:af4573f14ae61be3fbc3a473158ddfc1420f345410385809fd782e0d79e9215c"}, ] [package.dependencies] -typing-extensions = ">=4.1.0" +typing_extensions = ">=4.14.0" [package.extras] msgpack = ["msgpack (>=0.5.6)"] @@ -3198,125 +2696,101 @@ files = [ {file = "numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48"}, ] -[[package]] -name = "openai" -version = "2.9.0" -description = "The official Python library for the openai API" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "openai-2.9.0-py3-none-any.whl", hash = "sha256:0d168a490fbb45630ad508a6f3022013c155a68fd708069b6a1a01a5e8f0ffad"}, - {file = "openai-2.9.0.tar.gz", hash = "sha256:b52ec65727fc8f1eed2fbc86c8eac0998900c7ef63aa2eb5c24b69717c56fa5f"}, -] - -[package.dependencies] -anyio = ">=3.5.0,<5" -distro = ">=1.7.0,<2" -httpx = ">=0.23.0,<1" -jiter = ">=0.10.0,<1" -pydantic = ">=1.9.0,<3" -sniffio = "*" -tqdm = ">4" -typing-extensions = ">=4.11,<5" - -[package.extras] -aiohttp = ["aiohttp", "httpx-aiohttp (>=0.1.9)"] -datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] -realtime = ["websockets (>=13,<16)"] -voice-helpers = ["numpy (>=2.0.2)", "sounddevice (>=0.5.1)"] - [[package]] name = "orjson" -version = "3.11.3" +version = "3.11.5" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "orjson-3.11.3-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:29cb1f1b008d936803e2da3d7cba726fc47232c45df531b29edf0b232dd737e7"}, - {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97dceed87ed9139884a55db8722428e27bd8452817fbf1869c58b49fecab1120"}, - {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:58533f9e8266cb0ac298e259ed7b4d42ed3fa0b78ce76860626164de49e0d467"}, - {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c212cfdd90512fe722fa9bd620de4d46cda691415be86b2e02243242ae81873"}, - {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff835b5d3e67d9207343effb03760c00335f8b5285bfceefd4dc967b0e48f6a"}, - {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5aa4682912a450c2db89cbd92d356fef47e115dffba07992555542f344d301b"}, - {file = "orjson-3.11.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d18dd34ea2e860553a579df02041845dee0af8985dff7f8661306f95504ddf"}, - {file = "orjson-3.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d8b11701bc43be92ea42bd454910437b355dfb63696c06fe953ffb40b5f763b4"}, - {file = "orjson-3.11.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:90368277087d4af32d38bd55f9da2ff466d25325bf6167c8f382d8ee40cb2bbc"}, - {file = "orjson-3.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fd7ff459fb393358d3a155d25b275c60b07a2c83dcd7ea962b1923f5a1134569"}, - {file = "orjson-3.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f8d902867b699bcd09c176a280b1acdab57f924489033e53d0afe79817da37e6"}, - {file = "orjson-3.11.3-cp310-cp310-win32.whl", hash = "sha256:bb93562146120bb51e6b154962d3dadc678ed0fce96513fa6bc06599bb6f6edc"}, - {file = "orjson-3.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:976c6f1975032cc327161c65d4194c549f2589d88b105a5e3499429a54479770"}, - {file = "orjson-3.11.3-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9d2ae0cc6aeb669633e0124531f342a17d8e97ea999e42f12a5ad4adaa304c5f"}, - {file = "orjson-3.11.3-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:ba21dbb2493e9c653eaffdc38819b004b7b1b246fb77bfc93dc016fe664eac91"}, - {file = "orjson-3.11.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00f1a271e56d511d1569937c0447d7dce5a99a33ea0dec76673706360a051904"}, - {file = "orjson-3.11.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b67e71e47caa6680d1b6f075a396d04fa6ca8ca09aafb428731da9b3ea32a5a6"}, - {file = "orjson-3.11.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7d012ebddffcce8c85734a6d9e5f08180cd3857c5f5a3ac70185b43775d043d"}, - {file = "orjson-3.11.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd759f75d6b8d1b62012b7f5ef9461d03c804f94d539a5515b454ba3a6588038"}, - {file = "orjson-3.11.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6890ace0809627b0dff19cfad92d69d0fa3f089d3e359a2a532507bb6ba34efb"}, - {file = "orjson-3.11.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9d4a5e041ae435b815e568537755773d05dac031fee6a57b4ba70897a44d9d2"}, - {file = "orjson-3.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d68bf97a771836687107abfca089743885fb664b90138d8761cce61d5625d55"}, - {file = "orjson-3.11.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bfc27516ec46f4520b18ef645864cee168d2a027dbf32c5537cb1f3e3c22dac1"}, - {file = "orjson-3.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f66b001332a017d7945e177e282a40b6997056394e3ed7ddb41fb1813b83e824"}, - {file = "orjson-3.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:212e67806525d2561efbfe9e799633b17eb668b8964abed6b5319b2f1cfbae1f"}, - {file = "orjson-3.11.3-cp311-cp311-win32.whl", hash = "sha256:6e8e0c3b85575a32f2ffa59de455f85ce002b8bdc0662d6b9c2ed6d80ab5d204"}, - {file = "orjson-3.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:6be2f1b5d3dc99a5ce5ce162fc741c22ba9f3443d3dd586e6a1211b7bc87bc7b"}, - {file = "orjson-3.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:fafb1a99d740523d964b15c8db4eabbfc86ff29f84898262bf6e3e4c9e97e43e"}, - {file = "orjson-3.11.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8c752089db84333e36d754c4baf19c0e1437012242048439c7e80eb0e6426e3b"}, - {file = "orjson-3.11.3-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:9b8761b6cf04a856eb544acdd82fc594b978f12ac3602d6374a7edb9d86fd2c2"}, - {file = "orjson-3.11.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b13974dc8ac6ba22feaa867fc19135a3e01a134b4f7c9c28162fed4d615008a"}, - {file = "orjson-3.11.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f83abab5bacb76d9c821fd5c07728ff224ed0e52d7a71b7b3de822f3df04e15c"}, - {file = "orjson-3.11.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6fbaf48a744b94091a56c62897b27c31ee2da93d826aa5b207131a1e13d4064"}, - {file = "orjson-3.11.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc779b4f4bba2847d0d2940081a7b6f7b5877e05408ffbb74fa1faf4a136c424"}, - {file = "orjson-3.11.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd4b909ce4c50faa2192da6bb684d9848d4510b736b0611b6ab4020ea6fd2d23"}, - {file = "orjson-3.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:524b765ad888dc5518bbce12c77c2e83dee1ed6b0992c1790cc5fb49bb4b6667"}, - {file = "orjson-3.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:84fd82870b97ae3cdcea9d8746e592b6d40e1e4d4527835fc520c588d2ded04f"}, - {file = "orjson-3.11.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fbecb9709111be913ae6879b07bafd4b0785b44c1eb5cac8ac76da048b3885a1"}, - {file = "orjson-3.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9dba358d55aee552bd868de348f4736ca5a4086d9a62e2bfbbeeb5629fe8b0cc"}, - {file = "orjson-3.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eabcf2e84f1d7105f84580e03012270c7e97ecb1fb1618bda395061b2a84a049"}, - {file = "orjson-3.11.3-cp312-cp312-win32.whl", hash = "sha256:3782d2c60b8116772aea8d9b7905221437fdf53e7277282e8d8b07c220f96cca"}, - {file = "orjson-3.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:79b44319268af2eaa3e315b92298de9a0067ade6e6003ddaef72f8e0bedb94f1"}, - {file = "orjson-3.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:0e92a4e83341ef79d835ca21b8bd13e27c859e4e9e4d7b63defc6e58462a3710"}, - {file = "orjson-3.11.3-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:af40c6612fd2a4b00de648aa26d18186cd1322330bd3a3cc52f87c699e995810"}, - {file = "orjson-3.11.3-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:9f1587f26c235894c09e8b5b7636a38091a9e6e7fe4531937534749c04face43"}, - {file = "orjson-3.11.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61dcdad16da5bb486d7227a37a2e789c429397793a6955227cedbd7252eb5a27"}, - {file = "orjson-3.11.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11c6d71478e2cbea0a709e8a06365fa63da81da6498a53e4c4f065881d21ae8f"}, - {file = "orjson-3.11.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff94112e0098470b665cb0ed06efb187154b63649403b8d5e9aedeb482b4548c"}, - {file = "orjson-3.11.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae8b756575aaa2a855a75192f356bbda11a89169830e1439cfb1a3e1a6dde7be"}, - {file = "orjson-3.11.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9416cc19a349c167ef76135b2fe40d03cea93680428efee8771f3e9fb66079d"}, - {file = "orjson-3.11.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b822caf5b9752bc6f246eb08124c3d12bf2175b66ab74bac2ef3bbf9221ce1b2"}, - {file = "orjson-3.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:414f71e3bdd5573893bf5ecdf35c32b213ed20aa15536fe2f588f946c318824f"}, - {file = "orjson-3.11.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:828e3149ad8815dc14468f36ab2a4b819237c155ee1370341b91ea4c8672d2ee"}, - {file = "orjson-3.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac9e05f25627ffc714c21f8dfe3a579445a5c392a9c8ae7ba1d0e9fb5333f56e"}, - {file = "orjson-3.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e44fbe4000bd321d9f3b648ae46e0196d21577cf66ae684a96ff90b1f7c93633"}, - {file = "orjson-3.11.3-cp313-cp313-win32.whl", hash = "sha256:2039b7847ba3eec1f5886e75e6763a16e18c68a63efc4b029ddf994821e2e66b"}, - {file = "orjson-3.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:29be5ac4164aa8bdcba5fa0700a3c9c316b411d8ed9d39ef8a882541bd452fae"}, - {file = "orjson-3.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:18bd1435cb1f2857ceb59cfb7de6f92593ef7b831ccd1b9bfb28ca530e539dce"}, - {file = "orjson-3.11.3-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cf4b81227ec86935568c7edd78352a92e97af8da7bd70bdfdaa0d2e0011a1ab4"}, - {file = "orjson-3.11.3-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:bc8bc85b81b6ac9fc4dae393a8c159b817f4c2c9dee5d12b773bddb3b95fc07e"}, - {file = "orjson-3.11.3-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:88dcfc514cfd1b0de038443c7b3e6a9797ffb1b3674ef1fd14f701a13397f82d"}, - {file = "orjson-3.11.3-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d61cd543d69715d5fc0a690c7c6f8dcc307bc23abef9738957981885f5f38229"}, - {file = "orjson-3.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2b7b153ed90ababadbef5c3eb39549f9476890d339cf47af563aea7e07db2451"}, - {file = "orjson-3.11.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7909ae2460f5f494fecbcd10613beafe40381fd0316e35d6acb5f3a05bfda167"}, - {file = "orjson-3.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2030c01cbf77bc67bee7eef1e7e31ecf28649353987775e3583062c752da0077"}, - {file = "orjson-3.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a0169ebd1cbd94b26c7a7ad282cf5c2744fce054133f959e02eb5265deae1872"}, - {file = "orjson-3.11.3-cp314-cp314-win32.whl", hash = "sha256:0c6d7328c200c349e3a4c6d8c83e0a5ad029bdc2d417f234152bf34842d0fc8d"}, - {file = "orjson-3.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:317bbe2c069bbc757b1a2e4105b64aacd3bc78279b66a6b9e51e846e4809f804"}, - {file = "orjson-3.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:e8f6a7a27d7b7bec81bd5924163e9af03d49bbb63013f107b48eb5d16db711bc"}, - {file = "orjson-3.11.3-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:56afaf1e9b02302ba636151cfc49929c1bb66b98794291afd0e5f20fecaf757c"}, - {file = "orjson-3.11.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:913f629adef31d2d350d41c051ce7e33cf0fd06a5d1cb28d49b1899b23b903aa"}, - {file = "orjson-3.11.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0a23b41f8f98b4e61150a03f83e4f0d566880fe53519d445a962929a4d21045"}, - {file = "orjson-3.11.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d721fee37380a44f9d9ce6c701b3960239f4fb3d5ceea7f31cbd43882edaa2f"}, - {file = "orjson-3.11.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73b92a5b69f31b1a58c0c7e31080aeaec49c6e01b9522e71ff38d08f15aa56de"}, - {file = "orjson-3.11.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d2489b241c19582b3f1430cc5d732caefc1aaf378d97e7fb95b9e56bed11725f"}, - {file = "orjson-3.11.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5189a5dab8b0312eadaf9d58d3049b6a52c454256493a557405e77a3d67ab7f"}, - {file = "orjson-3.11.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9d8787bdfbb65a85ea76d0e96a3b1bed7bf0fbcb16d40408dc1172ad784a49d2"}, - {file = "orjson-3.11.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:8e531abd745f51f8035e207e75e049553a86823d189a51809c078412cefb399a"}, - {file = "orjson-3.11.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:8ab962931015f170b97a3dd7bd933399c1bae8ed8ad0fb2a7151a5654b6941c7"}, - {file = "orjson-3.11.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:124d5ba71fee9c9902c4a7baa9425e663f7f0aecf73d31d54fe3dd357d62c1a7"}, - {file = "orjson-3.11.3-cp39-cp39-win32.whl", hash = "sha256:22724d80ee5a815a44fc76274bb7ba2e7464f5564aacb6ecddaa9970a83e3225"}, - {file = "orjson-3.11.3-cp39-cp39-win_amd64.whl", hash = "sha256:215c595c792a87d4407cb72dd5e0f6ee8e694ceeb7f9102b533c5a9bf2a916bb"}, - {file = "orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a"}, + {file = "orjson-3.11.5-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:df9eadb2a6386d5ea2bfd81309c505e125cfc9ba2b1b99a97e60985b0b3665d1"}, + {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc70da619744467d8f1f49a8cadae5ec7bbe054e5232d95f92ed8737f8c5870"}, + {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:073aab025294c2f6fc0807201c76fdaed86f8fc4be52c440fb78fbb759a1ac09"}, + {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:835f26fa24ba0bb8c53ae2a9328d1706135b74ec653ed933869b74b6909e63fd"}, + {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667c132f1f3651c14522a119e4dd631fad98761fa960c55e8e7430bb2a1ba4ac"}, + {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42e8961196af655bb5e63ce6c60d25e8798cd4dfbc04f4203457fa3869322c2e"}, + {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75412ca06e20904c19170f8a24486c4e6c7887dea591ba18a1ab572f1300ee9f"}, + {file = "orjson-3.11.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6af8680328c69e15324b5af3ae38abbfcf9cbec37b5346ebfd52339c3d7e8a18"}, + {file = "orjson-3.11.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a86fe4ff4ea523eac8f4b57fdac319faf037d3c1be12405e6a7e86b3fbc4756a"}, + {file = "orjson-3.11.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e607b49b1a106ee2086633167033afbd63f76f2999e9236f638b06b112b24ea7"}, + {file = "orjson-3.11.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7339f41c244d0eea251637727f016b3d20050636695bc78345cce9029b189401"}, + {file = "orjson-3.11.5-cp310-cp310-win32.whl", hash = "sha256:8be318da8413cdbbce77b8c5fac8d13f6eb0f0db41b30bb598631412619572e8"}, + {file = "orjson-3.11.5-cp310-cp310-win_amd64.whl", hash = "sha256:b9f86d69ae822cabc2a0f6c099b43e8733dda788405cba2665595b7e8dd8d167"}, + {file = "orjson-3.11.5-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9c8494625ad60a923af6b2b0bd74107146efe9b55099e20d7740d995f338fcd8"}, + {file = "orjson-3.11.5-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:7bb2ce0b82bc9fd1168a513ddae7a857994b780b2945a8c51db4ab1c4b751ebc"}, + {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67394d3becd50b954c4ecd24ac90b5051ee7c903d167459f93e77fc6f5b4c968"}, + {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:298d2451f375e5f17b897794bcc3e7b821c0f32b4788b9bcae47ada24d7f3cf7"}, + {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa5e4244063db8e1d87e0f54c3f7522f14b2dc937e65d5241ef0076a096409fd"}, + {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1db2088b490761976c1b2e956d5d4e6409f3732e9d79cfa69f876c5248d1baf9"}, + {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2ed66358f32c24e10ceea518e16eb3549e34f33a9d51f99ce23b0251776a1ef"}, + {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2021afda46c1ed64d74b555065dbd4c2558d510d8cec5ea6a53001b3e5e82a9"}, + {file = "orjson-3.11.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b42ffbed9128e547a1647a3e50bc88ab28ae9daa61713962e0d3dd35e820c125"}, + {file = "orjson-3.11.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8d5f16195bb671a5dd3d1dbea758918bada8f6cc27de72bd64adfbd748770814"}, + {file = "orjson-3.11.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c0e5d9f7a0227df2927d343a6e3859bebf9208b427c79bd31949abcc2fa32fa5"}, + {file = "orjson-3.11.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:23d04c4543e78f724c4dfe656b3791b5f98e4c9253e13b2636f1af5d90e4a880"}, + {file = "orjson-3.11.5-cp311-cp311-win32.whl", hash = "sha256:c404603df4865f8e0afe981aa3c4b62b406e6d06049564d58934860b62b7f91d"}, + {file = "orjson-3.11.5-cp311-cp311-win_amd64.whl", hash = "sha256:9645ef655735a74da4990c24ffbd6894828fbfa117bc97c1edd98c282ecb52e1"}, + {file = "orjson-3.11.5-cp311-cp311-win_arm64.whl", hash = "sha256:1cbf2735722623fcdee8e712cbaaab9e372bbcb0c7924ad711b261c2eccf4a5c"}, + {file = "orjson-3.11.5-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:334e5b4bff9ad101237c2d799d9fd45737752929753bf4faf4b207335a416b7d"}, + {file = "orjson-3.11.5-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:ff770589960a86eae279f5d8aa536196ebda8273a2a07db2a54e82b93bc86626"}, + {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed24250e55efbcb0b35bed7caaec8cedf858ab2f9f2201f17b8938c618c8ca6f"}, + {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a66d7769e98a08a12a139049aac2f0ca3adae989817f8c43337455fbc7669b85"}, + {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:86cfc555bfd5794d24c6a1903e558b50644e5e68e6471d66502ce5cb5fdef3f9"}, + {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a230065027bc2a025e944f9d4714976a81e7ecfa940923283bca7bbc1f10f626"}, + {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b29d36b60e606df01959c4b982729c8845c69d1963f88686608be9ced96dbfaa"}, + {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c74099c6b230d4261fdc3169d50efc09abf38ace1a42ea2f9994b1d79153d477"}, + {file = "orjson-3.11.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e697d06ad57dd0c7a737771d470eedc18e68dfdefcdd3b7de7f33dfda5b6212e"}, + {file = "orjson-3.11.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e08ca8a6c851e95aaecc32bc44a5aa75d0ad26af8cdac7c77e4ed93acf3d5b69"}, + {file = "orjson-3.11.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e8b5f96c05fce7d0218df3fdfeb962d6b8cfff7e3e20264306b46dd8b217c0f3"}, + {file = "orjson-3.11.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ddbfdb5099b3e6ba6d6ea818f61997bb66de14b411357d24c4612cf1ebad08ca"}, + {file = "orjson-3.11.5-cp312-cp312-win32.whl", hash = "sha256:9172578c4eb09dbfcf1657d43198de59b6cef4054de385365060ed50c458ac98"}, + {file = "orjson-3.11.5-cp312-cp312-win_amd64.whl", hash = "sha256:2b91126e7b470ff2e75746f6f6ee32b9ab67b7a93c8ba1d15d3a0caaf16ec875"}, + {file = "orjson-3.11.5-cp312-cp312-win_arm64.whl", hash = "sha256:acbc5fac7e06777555b0722b8ad5f574739e99ffe99467ed63da98f97f9ca0fe"}, + {file = "orjson-3.11.5-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3b01799262081a4c47c035dd77c1301d40f568f77cc7ec1bb7db5d63b0a01629"}, + {file = "orjson-3.11.5-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:61de247948108484779f57a9f406e4c84d636fa5a59e411e6352484985e8a7c3"}, + {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:894aea2e63d4f24a7f04a1908307c738d0dce992e9249e744b8f4e8dd9197f39"}, + {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ddc21521598dbe369d83d4d40338e23d4101dad21dae0e79fa20465dbace019f"}, + {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7cce16ae2f5fb2c53c3eafdd1706cb7b6530a67cc1c17abe8ec747f5cd7c0c51"}, + {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e46c762d9f0e1cfb4ccc8515de7f349abbc95b59cb5a2bd68df5973fdef913f8"}, + {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7345c759276b798ccd6d77a87136029e71e66a8bbf2d2755cbdde1d82e78706"}, + {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75bc2e59e6a2ac1dd28901d07115abdebc4563b5b07dd612bf64260a201b1c7f"}, + {file = "orjson-3.11.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:54aae9b654554c3b4edd61896b978568c6daa16af96fa4681c9b5babd469f863"}, + {file = "orjson-3.11.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4bdd8d164a871c4ec773f9de0f6fe8769c2d6727879c37a9666ba4183b7f8228"}, + {file = "orjson-3.11.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a261fef929bcf98a60713bf5e95ad067cea16ae345d9a35034e73c3990e927d2"}, + {file = "orjson-3.11.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c028a394c766693c5c9909dec76b24f37e6a1b91999e8d0c0d5feecbe93c3e05"}, + {file = "orjson-3.11.5-cp313-cp313-win32.whl", hash = "sha256:2cc79aaad1dfabe1bd2d50ee09814a1253164b3da4c00a78c458d82d04b3bdef"}, + {file = "orjson-3.11.5-cp313-cp313-win_amd64.whl", hash = "sha256:ff7877d376add4e16b274e35a3f58b7f37b362abf4aa31863dadacdd20e3a583"}, + {file = "orjson-3.11.5-cp313-cp313-win_arm64.whl", hash = "sha256:59ac72ea775c88b163ba8d21b0177628bd015c5dd060647bbab6e22da3aad287"}, + {file = "orjson-3.11.5-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e446a8ea0a4c366ceafc7d97067bfd55292969143b57e3c846d87fc701e797a0"}, + {file = "orjson-3.11.5-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:53deb5addae9c22bbe3739298f5f2196afa881ea75944e7720681c7080909a81"}, + {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82cd00d49d6063d2b8791da5d4f9d20539c5951f965e45ccf4e96d33505ce68f"}, + {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3fd15f9fc8c203aeceff4fda211157fad114dde66e92e24097b3647a08f4ee9e"}, + {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9df95000fbe6777bf9820ae82ab7578e8662051bb5f83d71a28992f539d2cda7"}, + {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92a8d676748fca47ade5bc3da7430ed7767afe51b2f8100e3cd65e151c0eaceb"}, + {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa0f513be38b40234c77975e68805506cad5d57b3dfd8fe3baa7f4f4051e15b4"}, + {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1863e75b92891f553b7922ce4ee10ed06db061e104f2b7815de80cdcb135ad"}, + {file = "orjson-3.11.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4be86b58e9ea262617b8ca6251a2f0d63cc132a6da4b5fcc8e0a4128782c829"}, + {file = "orjson-3.11.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:b923c1c13fa02084eb38c9c065afd860a5cff58026813319a06949c3af5732ac"}, + {file = "orjson-3.11.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1b6bd351202b2cd987f35a13b5e16471cf4d952b42a73c391cc537974c43ef6d"}, + {file = "orjson-3.11.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bb150d529637d541e6af06bbe3d02f5498d628b7f98267ff87647584293ab439"}, + {file = "orjson-3.11.5-cp314-cp314-win32.whl", hash = "sha256:9cc1e55c884921434a84a0c3dd2699eb9f92e7b441d7f53f3941079ec6ce7499"}, + {file = "orjson-3.11.5-cp314-cp314-win_amd64.whl", hash = "sha256:a4f3cb2d874e03bc7767c8f88adaa1a9a05cecea3712649c3b58589ec7317310"}, + {file = "orjson-3.11.5-cp314-cp314-win_arm64.whl", hash = "sha256:38b22f476c351f9a1c43e5b07d8b5a02eb24a6ab8e75f700f7d479d4568346a5"}, + {file = "orjson-3.11.5-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1b280e2d2d284a6713b0cfec7b08918ebe57df23e3f76b27586197afca3cb1e9"}, + {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d8a112b274fae8c5f0f01954cb0480137072c271f3f4958127b010dfefaec"}, + {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0a2ae6f09ac7bd47d2d5a5305c1d9ed08ac057cda55bb0a49fa506f0d2da00"}, + {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0d87bd1896faac0d10b4f849016db81a63e4ec5df38757ffae84d45ab38aa71"}, + {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:801a821e8e6099b8c459ac7540b3c32dba6013437c57fdcaec205b169754f38c"}, + {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69a0f6ac618c98c74b7fbc8c0172ba86f9e01dbf9f62aa0b1776c2231a7bffe5"}, + {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fea7339bdd22e6f1060c55ac31b6a755d86a5b2ad3657f2669ec243f8e3b2bdb"}, + {file = "orjson-3.11.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4dad582bc93cef8f26513e12771e76385a7e6187fd713157e971c784112aad56"}, + {file = "orjson-3.11.5-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:0522003e9f7fba91982e83a97fec0708f5a714c96c4209db7104e6b9d132f111"}, + {file = "orjson-3.11.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7403851e430a478440ecc1258bcbacbfbd8175f9ac1e39031a7121dd0de05ff8"}, + {file = "orjson-3.11.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5f691263425d3177977c8d1dd896cde7b98d93cbf390b2544a090675e83a6a0a"}, + {file = "orjson-3.11.5-cp39-cp39-win32.whl", hash = "sha256:61026196a1c4b968e1b1e540563e277843082e9e97d78afa03eb89315af531f1"}, + {file = "orjson-3.11.5-cp39-cp39-win_amd64.whl", hash = "sha256:09b94b947ac08586af635ef922d69dc9bc63321527a3a04647f4986a73f4bd30"}, + {file = "orjson-3.11.5.tar.gz", hash = "sha256:82393ab47b4fe44ffd0a7659fa9cfaacc717eb617c93cde83795f14af5c2e9d5"}, ] [[package]] @@ -3735,108 +3209,101 @@ psutil = "*" [[package]] name = "pycares" -version = "4.11.0" +version = "5.0.1" description = "Python interface for c-ares" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "pycares-4.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:87dab618fe116f1936f8461df5970fcf0befeba7531a36b0a86321332ff9c20b"}, - {file = "pycares-4.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3db6b6439e378115572fa317053f3ee6eecb39097baafe9292320ff1a9df73e3"}, - {file = "pycares-4.11.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:742fbaa44b418237dbd6bf8cdab205c98b3edb334436a972ad341b0ea296fb47"}, - {file = "pycares-4.11.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:d2a3526dbf6cb01b355e8867079c9356a8df48706b4b099ac0bf59d4656e610d"}, - {file = "pycares-4.11.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:3d5300a598ad48bbf169fba1f2b2e4cf7ab229e7c1a48d8c1166f9ccf1755cb3"}, - {file = "pycares-4.11.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:066f3caa07c85e1a094aebd9e7a7bb3f3b2d97cff2276665693dd5c0cc81cf84"}, - {file = "pycares-4.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:dcd4a7761fdfb5aaac88adad0a734dd065c038f5982a8c4b0dd28efa0bd9cc7c"}, - {file = "pycares-4.11.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:83a7401d7520fa14b00d85d68bcca47a0676c69996e8515d53733972286f9739"}, - {file = "pycares-4.11.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:66c310773abe42479302abf064832f4a37c8d7f788f4d5ee0d43cbad35cf5ff4"}, - {file = "pycares-4.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:95bc81f83fadb67f7f87914f216a0e141555ee17fd7f56e25aa0cc165e99e53b"}, - {file = "pycares-4.11.0-cp310-cp310-win32.whl", hash = "sha256:1dbbf0cfb39be63598b4cdc2522960627bf2f523e49c4349fb64b0499902ec7c"}, - {file = "pycares-4.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:dde02314eefb85dce3cfdd747e8b44c69a94d442c0d7221b7de151ee4c93f0f5"}, - {file = "pycares-4.11.0-cp310-cp310-win_arm64.whl", hash = "sha256:9518514e3e85646bac798d94d34bf5b8741ee0cb580512e8450ce884f526b7cf"}, - {file = "pycares-4.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c2971af3a4094280f7c24293ff4d361689c175c1ebcbea6b3c1560eaff7cb240"}, - {file = "pycares-4.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5d69e2034160e1219665decb8140e439afc7a7afcfd4adff08eb0f6142405c3e"}, - {file = "pycares-4.11.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3bd81ad69f607803f531ff5cfa1262391fa06e78488c13495cee0f70d02e0287"}, - {file = "pycares-4.11.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:0aed0974eab3131d832e7e84a73ddb0dddbc57393cd8c0788d68a759a78c4a7b"}, - {file = "pycares-4.11.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:30d197180af626bb56f17e1fa54640838d7d12ed0f74665a3014f7155435b199"}, - {file = "pycares-4.11.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cb711a66246561f1cae51244deef700eef75481a70d99611fd3c8ab5bd69ab49"}, - {file = "pycares-4.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7aba9a312a620052133437f2363aae90ae4695ee61cb2ee07cbb9951d4c69ddd"}, - {file = "pycares-4.11.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c2af7a9d3afb63da31df1456d38b91555a6c147710a116d5cc70ab1e9f457a4f"}, - {file = "pycares-4.11.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d5fe089be67bc5927f0c0bd60c082c79f22cf299635ee3ddd370ae2a6e8b4ae0"}, - {file = "pycares-4.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:35ff1ec260372c97ed688efd5b3c6e5481f2274dea08f6c4ea864c195a9673c6"}, - {file = "pycares-4.11.0-cp311-cp311-win32.whl", hash = "sha256:ff3d25883b7865ea34c00084dd22a7be7c58fd3131db6b25c35eafae84398f9d"}, - {file = "pycares-4.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:f4695153333607e63068580f2979b377b641a03bc36e02813659ffbea2b76fe2"}, - {file = "pycares-4.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:dc54a21586c096df73f06f9bdf594e8d86d7be84e5d4266358ce81c04c3cc88c"}, - {file = "pycares-4.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b93d624560ba52287873bacff70b42c99943821ecbc810b959b0953560f53c36"}, - {file = "pycares-4.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:775d99966e28c8abd9910ddef2de0f1e173afc5a11cea9f184613c747373ab80"}, - {file = "pycares-4.11.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:84fde689557361764f052850a2d68916050adbfd9321f6105aca1d8f1a9bd49b"}, - {file = "pycares-4.11.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:30ceed06f3bf5eff865a34d21562c25a7f3dad0ed336b9dd415330e03a6c50c4"}, - {file = "pycares-4.11.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:97d971b3a88a803bb95ff8a40ea4d68da59319eb8b59e924e318e2560af8c16d"}, - {file = "pycares-4.11.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:2d5cac829da91ade70ce1af97dad448c6cd4778b48facbce1b015e16ced93642"}, - {file = "pycares-4.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ee1ea367835eb441d246164c09d1f9703197af4425fc6865cefcde9e2ca81f85"}, - {file = "pycares-4.11.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3139ec1f4450a4b253386035c5ecd2722582ae3320a456df5021ffe3f174260a"}, - {file = "pycares-4.11.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5d70324ca1d82c6c4b00aa678347f7560d1ef2ce1d181978903459a97751543a"}, - {file = "pycares-4.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e2f8d9cfe0eb3a2997fde5df99b1aaea5a46dabfcfcac97b2d05f027c2cd5e28"}, - {file = "pycares-4.11.0-cp312-cp312-win32.whl", hash = "sha256:1571a7055c03a95d5270c914034eac7f8bfa1b432fc1de53d871b821752191a4"}, - {file = "pycares-4.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:7570e0b50db619b2ee370461c462617225dc3a3f63f975c6f117e2f0c94f82ca"}, - {file = "pycares-4.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:f199702740f3b766ed8c70efb885538be76cb48cd0cb596b948626f0b825e07a"}, - {file = "pycares-4.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c296ab94d1974f8d2f76c499755a9ce31ffd4986e8898ef19b90e32525f7d84"}, - {file = "pycares-4.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0fcd3a8bac57a0987d9b09953ba0f8703eb9dca7c77f7051d8c2ed001185be8"}, - {file = "pycares-4.11.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:bac55842047567ddae177fb8189b89a60633ac956d5d37260f7f71b517fd8b87"}, - {file = "pycares-4.11.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:4da2e805ed8c789b9444ef4053f6ef8040cd13b0c1ca6d3c4fe6f9369c458cb4"}, - {file = "pycares-4.11.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:ea785d1f232b42b325578f0c8a2fa348192e182cc84a1e862896076a4a2ba2a7"}, - {file = "pycares-4.11.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:aa160dc9e785212c49c12bb891e242c949758b99542946cc8e2098ef391f93b0"}, - {file = "pycares-4.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7830709c23bbc43fbaefbb3dde57bdd295dc86732504b9d2e65044df8fd5e9fb"}, - {file = "pycares-4.11.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ef1ab7abbd238bb2dbbe871c3ea39f5a7fc63547c015820c1e24d0d494a1689"}, - {file = "pycares-4.11.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a4060d8556c908660512d42df1f4a874e4e91b81f79e3a9090afedc7690ea5ba"}, - {file = "pycares-4.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a98fac4a3d4f780817016b6f00a8a2c2f41df5d25dfa8e5b1aa0d783645a6566"}, - {file = "pycares-4.11.0-cp313-cp313-win32.whl", hash = "sha256:faa8321bc2a366189dcf87b3823e030edf5ac97a6b9a7fc99f1926c4bf8ef28e"}, - {file = "pycares-4.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:6f74b1d944a50fa12c5006fd10b45e1a45da0c5d15570919ce48be88e428264c"}, - {file = "pycares-4.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f7581793d8bb3014028b8397f6f80b99db8842da58f4409839c29b16397ad"}, - {file = "pycares-4.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:df0a17f4e677d57bca3624752bbb515316522ad1ce0de07ed9d920e6c4ee5d35"}, - {file = "pycares-4.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3b44e54cad31d3c3be5e8149ac36bc1c163ec86e0664293402f6f846fb22ad00"}, - {file = "pycares-4.11.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:80752133442dc7e6dd9410cec227c49f69283c038c316a8585cca05ec32c2766"}, - {file = "pycares-4.11.0-cp314-cp314-manylinux_2_28_ppc64le.whl", hash = "sha256:84b0b402dd333403fdce0e204aef1ef834d839c439c0c1aa143dc7d1237bb197"}, - {file = "pycares-4.11.0-cp314-cp314-manylinux_2_28_s390x.whl", hash = "sha256:c0eec184df42fc82e43197e073f9cc8f93b25ad2f11f230c64c2dc1c80dbc078"}, - {file = "pycares-4.11.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:ee751409322ff10709ee867d5aea1dc8431eec7f34835f0f67afd016178da134"}, - {file = "pycares-4.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1732db81e348bfce19c9bf9448ba660aea03042eeeea282824da1604a5bd4dcf"}, - {file = "pycares-4.11.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:702d21823996f139874aba5aa9bb786d69e93bde6e3915b99832eb4e335d31ae"}, - {file = "pycares-4.11.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:218619b912cef7c64a339ab0e231daea10c994a05699740714dff8c428b9694a"}, - {file = "pycares-4.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:719f7ddff024fdacde97b926b4b26d0cc25901d5ef68bb994a581c420069936d"}, - {file = "pycares-4.11.0-cp314-cp314-win32.whl", hash = "sha256:d552fb2cb513ce910d1dc22dbba6420758a991a356f3cd1b7ec73a9e31f94d01"}, - {file = "pycares-4.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:23d50a0842e8dbdddf870a7218a7ab5053b68892706b3a391ecb3d657424d266"}, - {file = "pycares-4.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:836725754c32363d2c5d15b931b3ebd46b20185c02e850672cb6c5f0452c1e80"}, - {file = "pycares-4.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c9d839b5700542b27c1a0d359cbfad6496341e7c819c7fea63db9588857065ed"}, - {file = "pycares-4.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:31b85ad00422b38f426e5733a71dfb7ee7eb65a99ea328c508d4f552b1760dc8"}, - {file = "pycares-4.11.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:cdac992206756b024b371760c55719eb5cd9d6b2cb25a8d5a04ae1b0ff426232"}, - {file = "pycares-4.11.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:ffb22cee640bc12ee0e654eba74ecfb59e2e0aebc5bccc3cc7ef92f487008af7"}, - {file = "pycares-4.11.0-cp314-cp314t-manylinux_2_28_s390x.whl", hash = "sha256:00538826d2eaf4a0e4becb0753b0ac8d652334603c445c9566c9eb273657eb4c"}, - {file = "pycares-4.11.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:29daa36548c04cdcd1a78ae187a4b7b003f0b357a2f4f1f98f9863373eedc759"}, - {file = "pycares-4.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cf306f3951740d7bed36149a6d8d656a7d5432dd4bbc6af3bb6554361fc87401"}, - {file = "pycares-4.11.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:386da2581db4ea2832629e275c061103b0be32f9391c5dfaea7f6040951950ad"}, - {file = "pycares-4.11.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:45d3254a694459fdb0640ef08724ca9d4b4f6ff6d7161c9b526d7d2e2111379e"}, - {file = "pycares-4.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eddf5e520bb88b23b04ac1f28f5e9a7c77c718b8b4af3a4a7a2cc4a600f34502"}, - {file = "pycares-4.11.0-cp314-cp314t-win32.whl", hash = "sha256:8a75a406432ce39ce0ca41edff7486df6c970eb0fe5cfbe292f195a6b8654461"}, - {file = "pycares-4.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:3784b80d797bcc2ff2bf3d4b27f46d8516fe1707ff3b82c2580dc977537387f9"}, - {file = "pycares-4.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:afc6503adf8b35c21183b9387be64ca6810644ef54c9ef6c99d1d5635c01601b"}, - {file = "pycares-4.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5e1ab899bb0763dea5d6569300aab3a205572e6e2d0ef1a33b8cf2b86d1312a4"}, - {file = "pycares-4.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9d0c543bdeefa4794582ef48f3c59e5e7a43d672a4bfad9cbbd531e897911690"}, - {file = "pycares-4.11.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:5344d52efa37df74728505a81dd52c15df639adffd166f7ddca7a6318ecdb605"}, - {file = "pycares-4.11.0-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:b50ca218a3e2e23cbda395fd002d030385202fbb8182aa87e11bea0a568bd0b8"}, - {file = "pycares-4.11.0-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:30feeab492ac609f38a0d30fab3dc1789bd19c48f725b2955bcaaef516e32a21"}, - {file = "pycares-4.11.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:6195208b16cce1a7b121727710a6f78e8403878c1017ab5a3f92158b048cec34"}, - {file = "pycares-4.11.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:77bf82dc0beb81262bf1c7f546e1c1fde4992e5c8a2343b867ca201b85f9e1aa"}, - {file = "pycares-4.11.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:aca981fc00c8af8d5b9254ea5c2f276df8ece089b081af1ef4856fbcfc7c698a"}, - {file = "pycares-4.11.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:96e07d5a8b733d753e37d1f7138e7321d2316bb3f0f663ab4e3d500fabc82807"}, - {file = "pycares-4.11.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9a00408105901ede92e318eecb46d0e661d7d093d0a9b1224c71b5dd94f79e83"}, - {file = "pycares-4.11.0-cp39-cp39-win32.whl", hash = "sha256:910ce19a549f493fb55cfd1d7d70960706a03de6bfc896c1429fc5d6216df77e"}, - {file = "pycares-4.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:6f751f5a0e4913b2787f237c2c69c11a53f599269012feaa9fb86d7cef3aec26"}, - {file = "pycares-4.11.0-cp39-cp39-win_arm64.whl", hash = "sha256:f6c602c5e3615abbf43dbdf3c6c64c65e76e5aa23cb74e18466b55d4a2095468"}, - {file = "pycares-4.11.0.tar.gz", hash = "sha256:c863d9003ca0ce7df26429007859afd2a621d3276ed9fef154a9123db9252557"}, + {file = "pycares-5.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:adc592534a10fe24fd1a801173c46769f75b97c440c9162f5d402ee1ba3eaf51"}, + {file = "pycares-5.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8848bbea6b5c2a0f7c9d0231ee455c3ce976c5c85904e014b2e9aa636a34140e"}, + {file = "pycares-5.0.1-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5003cbbae0a943f49089cc149996c3d078cef482971d834535032d53558f4d48"}, + {file = "pycares-5.0.1-cp310-cp310-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc0cdeadb2892e7f0ab30b6a288a357441c21dcff2ce16e91fccbc9fae9d1e2a"}, + {file = "pycares-5.0.1-cp310-cp310-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:faa093af3bea365947325ec23ed24efe81dcb0efc1be7e19a36ba37108945237"}, + {file = "pycares-5.0.1-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dedd6d41bd09dbed7eeea84a30b4b6fd1cacf9523a3047e088b5e692cff13d84"}, + {file = "pycares-5.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d3eb5e6ba290efd8b543a2cb77ad938c3494250e6e0302ee2aa55c06bbe153cd"}, + {file = "pycares-5.0.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:58634f83992c81f438987b572d371825dae187d3a09d6e213edbe71fbb4ba18c"}, + {file = "pycares-5.0.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fe9ce4361809903261c4b055284ba91d94adedfd2202e0257920b9085d505e37"}, + {file = "pycares-5.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:965ec648814829788233155ef3f6d4d7e7d6183460d10f9c71859c504f8f488b"}, + {file = "pycares-5.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:171182baa32951fffd1568ba9b934a76f36ed86c6248855ec6f82bbb3954d604"}, + {file = "pycares-5.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:48ac858124728b8bac0591aa8361c683064fefe35794c29b3a954818c59f1e9b"}, + {file = "pycares-5.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c29ca77ff9712e20787201ca8e76ad89384771c0e058a0a4f3dc05afbc4b32de"}, + {file = "pycares-5.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f11424bf5cf6226d0b136ed47daa58434e377c61b62d0100d1de7793f8e34a72"}, + {file = "pycares-5.0.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d765afb52d579879f5c4f005763827d3b1eb86b23139e9614e6089c9f98db017"}, + {file = "pycares-5.0.1-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ea0d57ba5add4bfbcc40cbdfa92bbb8a5ef0c4c21881e26c7229d9bdc92a4533"}, + {file = "pycares-5.0.1-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae9ec2aa3553d33e6220aeb1a05f4853fb83fce4cec3e0dea2dc970338ea47dc"}, + {file = "pycares-5.0.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5c63fb2498b05e9f5670a1bf3b900c5d09343b3b6d5001a9714d593f9eb54de1"}, + {file = "pycares-5.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:71316f7a87c15a8d32127ff01374dc2c969c37410693cc0cf6532590b7f18e7a"}, + {file = "pycares-5.0.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a2117dffbb78615bfdb41ad77b17038689e4e01c66f153649e80d268c6228b4f"}, + {file = "pycares-5.0.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7d7c4f5d8b88b586ef2288142b806250020e6490b9f2bd8fd5f634a78fd20fcf"}, + {file = "pycares-5.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:433b9a4b5a7e10ef8aef0b957e6cd0bfc1bb5bc730d2729f04e93c91c25979c0"}, + {file = "pycares-5.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:cf2699883b88713670d3f9c0a1e44ac24c70aeace9f8c6aa7f0b9f222d5b08a5"}, + {file = "pycares-5.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:9528dc11749e5e098c996475b60f879e1db5a6cb3dd0cdc747530620bb1a8941"}, + {file = "pycares-5.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2ee551be4f3f3ac814ac8547586c464c9035e914f5122a534d25de147fa745e1"}, + {file = "pycares-5.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:252d4e5a52a68f825eaa90e16b595f9baee22c760f51e286ab612c6829b96de3"}, + {file = "pycares-5.0.1-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c1aa549b8c2f2e224215c793d660270778dcba9abc3b85abbc7c41eabe4f1e5"}, + {file = "pycares-5.0.1-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:db7c9c9f16e8311998667a7488e817f8cbeedec2447bac827c71804663f1437e"}, + {file = "pycares-5.0.1-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b9c4c8bb69bab863f677fa166653bb872bfa5d5a742f1f30bebc2d53b6e71db"}, + {file = "pycares-5.0.1-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09ef90da8da3026fcba4ed223bd71e8057608d5b3fec4f5990b52ae1e8c855cc"}, + {file = "pycares-5.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ce193ebd54f4c74538b751ebb0923a9208c234ff180589d4d3cec134c001840e"}, + {file = "pycares-5.0.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:36b9ff18ef231277f99a846feade50b417187a96f742689a9d08b9594e386de4"}, + {file = "pycares-5.0.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5e40ea4a0ef0c01a02ef7f7390a58c62d237d5ad48d36bc3245e9c2ac181cc22"}, + {file = "pycares-5.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3f323b0ddfd2c7896af6fba4f8851d34d3d13387566aa573d93330fb01cb1038"}, + {file = "pycares-5.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:bdc6bcafb72a97b3cdd529fc87210e59e67feb647a7e138110656023599b84da"}, + {file = "pycares-5.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:f8ef4c70c1edaf022875a8f9ff6c0c064f82831225acc91aa1b4f4d389e2e03a"}, + {file = "pycares-5.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7d1b2c6b152c65f14d0e12d741fabb78a487f0f0d22773eede8d8cfc97af612b"}, + {file = "pycares-5.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8c8ffcc9a48cfc296fe1aefc07d2c8e29a7f97e4bb366ce17effea6a38825f70"}, + {file = "pycares-5.0.1-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8efc38c2703e3530b823a4165a7b28d7ce0fdcf41960fb7a4ca834a0f8cfe79"}, + {file = "pycares-5.0.1-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e380bf6eff42c260f829a0a14547e13375e949053a966c23ca204a13647ef265"}, + {file = "pycares-5.0.1-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:35dd5858ee1246bd092a212b5e85a8ef70853f7cfaf16b99569bf4af3ae4695d"}, + {file = "pycares-5.0.1-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c257c6e7bf310cdb5823aa9d9a28f1e370fed8c653a968d38a954a8f8e0375ce"}, + {file = "pycares-5.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07711acb0ef75758f081fb7436acaccc91e8afd5ae34fd35d4edc44297e81f27"}, + {file = "pycares-5.0.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:30e5db1ae85cffb031dd8bc1b37903cd74c6d37eb737643bbca3ff2cd4bc6ae2"}, + {file = "pycares-5.0.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:efbe7f89425a14edbc94787042309be77cb3674415eb6079b356e1f9552ba747"}, + {file = "pycares-5.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5de9e7ce52d638d78723c24704eb032e60b96fbb6fe90c6b3110882987251377"}, + {file = "pycares-5.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:0e99af0a1ce015ab6cc6bd85ce158d95ed89fb3b654515f1d0989d1afcf11026"}, + {file = "pycares-5.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:2a511c9f3b11b7ce9f159c956ea1b8f2de7f419d7ca9fa24528d582cb015dbf9"}, + {file = "pycares-5.0.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e330e3561be259ad7a1b7b0ce282c872938625f76587fae7ac8d6bc5af1d0c3d"}, + {file = "pycares-5.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82bd37fec2a3fa62add30d4a3854720f7b051386e2f18e6e8f4ee94b89b5a7b0"}, + {file = "pycares-5.0.1-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:258c38aaa82ad1d565b4591cdb93d2c191be8e0a2c70926999c8e0b717a01f2a"}, + {file = "pycares-5.0.1-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ccc1b2df8a09ca20eefbe20b9f7a484d376525c0fb173cfadd692320013c6bc5"}, + {file = "pycares-5.0.1-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3c4dfc80cc8b43dc79e02a15486c58eead5cae0a40906d6be64e2522285b5b39"}, + {file = "pycares-5.0.1-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f498a6606247bfe896c2a4d837db711eb7b0ba23e409e16e4b23def4bada4b9d"}, + {file = "pycares-5.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a7d197835cdb4b202a3b12562b32799e27bb132262d4aa1ac3ee9d440e8ec22c"}, + {file = "pycares-5.0.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f78ab823732b050d658eb735d553726663c9bccdeeee0653247533a23eb2e255"}, + {file = "pycares-5.0.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f444ab7f318e9b2c209b45496fb07bff5e7ada606e15d5253a162964aa078527"}, + {file = "pycares-5.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9de80997de7538619b7dd28ec4371e5172e3f9480e4fc648726d3d5ba661ca05"}, + {file = "pycares-5.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:206ce9f3cb9d51f5065c81b23c22996230fbc2cf58ae22834c623631b2b473aa"}, + {file = "pycares-5.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:45fb3b07231120e8cb5b75be7f15f16115003e9251991dc37a3e5c63733d63b5"}, + {file = "pycares-5.0.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:602f3eac4b880a2527d21f52b2319cb10fde9225d103d338c4d0b2b07f136849"}, + {file = "pycares-5.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1c3736deef003f0c57bc4e7f94d54270d0824350a8f5ceaba3a20b2ce8fb427"}, + {file = "pycares-5.0.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e63328df86d37150ce697fb5d9313d1d468dd4dddee1d09342cb2ed241ce6ad9"}, + {file = "pycares-5.0.1-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57f6fd696213329d9a69b9664a68b1ff2a71ccbdc1fc928a42c9a92858c1ec5d"}, + {file = "pycares-5.0.1-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9d0878edabfbecb48a29e8769284003d8dbc05936122fe361849cd5fa52722e0"}, + {file = "pycares-5.0.1-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50e21f27a91be122e066ddd78c2d0d2769e547561481d8342a9d652a345b89f7"}, + {file = "pycares-5.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:97ceda969f5a5d5c6b15558b658c29e4301b3a2c4615523797b5f9d4ac74772e"}, + {file = "pycares-5.0.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4d1713e602ab09882c3e65499b2cc763bff0371117327cad704cf524268c2604"}, + {file = "pycares-5.0.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:954a379055d6c66b2e878b52235b382168d1a3230793ff44454019394aecac5e"}, + {file = "pycares-5.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:145d8a20f7fd1d58a2e49b7ef4309ec9bdcab479ac65c2e49480e20d3f890c23"}, + {file = "pycares-5.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:ebc9daba03c7ff3f62616c84c6cb37517445d15df00e1754852d6006039eb4a4"}, + {file = "pycares-5.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:e0a86eff6bf9e91d5dd8876b1b82ee45704f46b1104c24291d3dea2c1fc8ebcb"}, + {file = "pycares-5.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:89fbb801bd7328d38025ab3576eee697cf9eca1f45774a0353b6a68a867e5516"}, + {file = "pycares-5.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f760ed82ad8b7311ada58f9f68673e135ece3b1beb06d3ec8723a4f3d5dd824e"}, + {file = "pycares-5.0.1-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94cb140b78bde232f6eb64c95cdac08dac9ae1829bfee1c436932eea10aabd39"}, + {file = "pycares-5.0.1-cp39-cp39-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:83da4b2e30bb80a424337376af0bce1216d787821b71c74d2f2bf3d40ea0bcf9"}, + {file = "pycares-5.0.1-cp39-cp39-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:07260c6c0eff8aa809d6cd64010303098c7d0fe79176aba207d747c9ffc7a95a"}, + {file = "pycares-5.0.1-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e1630844c695fc41e760d653d775d03c61bf8c5ac259e90784f7f270e8c440c"}, + {file = "pycares-5.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8dc84c0bce595c572971c1a9c7a3b417465572382968faac9bfddebd60e946b4"}, + {file = "pycares-5.0.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:83115177cc0f1c8e6fbeb4e483d676f91d0ce90aad2933d5f0c87feccdc05688"}, + {file = "pycares-5.0.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:eb93ea76094c46fd4a1294eb49affcf849d36d9b939322009d2bee7d507fcb20"}, + {file = "pycares-5.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:534dd25083e7ba4c65fedbc94126bada53fe8de4466d9ca29b7aa2ab4eec36b4"}, + {file = "pycares-5.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:52901b7a15a3b99631021a90fa3d1451d42b47b977208928012bf8238f70ba13"}, + {file = "pycares-5.0.1-cp39-cp39-win_arm64.whl", hash = "sha256:153239d8c51f9e051d37867287ee1b283a201076e4cd9f4624ead30c86dfd5c9"}, + {file = "pycares-5.0.1.tar.gz", hash = "sha256:5a3c249c830432631439815f9a818463416f2a8cbdb1e988e78757de9ae75081"}, ] [package.dependencies] -cffi = {version = ">=1.5.0", markers = "python_version < \"3.14\""} +cffi = {version = ">=2.0.0b1", markers = "python_version >= \"3.14\""} [package.extras] idna = ["idna (>=2.1)"] @@ -3878,7 +3345,7 @@ version = "2.12.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["dev"] files = [ {file = "pydantic-2.12.2-py3-none-any.whl", hash = "sha256:25ff718ee909acd82f1ff9b1a4acfd781bb23ab3739adaa7144f19a6a4e231ae"}, {file = "pydantic-2.12.2.tar.gz", hash = "sha256:7b8fa15b831a4bbde9d5b84028641ac3080a4ca2cbd4a621a661687e741624fd"}, @@ -3900,7 +3367,7 @@ version = "2.41.4" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["dev"] files = [ {file = "pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e"}, {file = "pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b"}, @@ -4365,21 +3832,20 @@ pytest = ">=6.0.0" [[package]] name = "pytest-homeassistant-custom-component" -version = "0.13.301" +version = "0.13.315" description = "Experimental package to automatically extract test plugins for Home Assistant custom components" optional = false python-versions = ">=3.13" groups = ["dev"] files = [ - {file = "pytest_homeassistant_custom_component-0.13.301-py3-none-any.whl", hash = "sha256:2b6262d4ca76862ac47df3673631de80af29232514022862b2bf072da2faf264"}, - {file = "pytest_homeassistant_custom_component-0.13.301.tar.gz", hash = "sha256:503f347fc91f50155d520e3e90ff08f0a49c5936683453192682f64cfe50bdaa"}, + {file = "pytest_homeassistant_custom_component-0.13.315-py3-none-any.whl", hash = "sha256:1ac286b2e5c76c2dacc31ace8afdd30456da601d048ef84ee39a1739c08ab013"}, + {file = "pytest_homeassistant_custom_component-0.13.315.tar.gz", hash = "sha256:c52fc8596e43d6a4a43f4514094dabf0a7254089a22071473aa33d6558bb0880"}, ] [package.dependencies] coverage = "7.10.6" freezegun = "1.5.2" -go2rtc-client = "0.3.0" -homeassistant = "2025.12.4" +homeassistant = "2026.2.2" license-expression = "30.4.3" mock-open = "1.4.0" numpy = "2.3.2" @@ -4533,21 +3999,6 @@ files = [ {file = "python_direnv-0.2.2.tar.gz", hash = "sha256:0fe2fb834c901d675edcacc688689cfcf55cf06d9cf27dc7d3768a6c38c35f00"}, ] -[[package]] -name = "python-dotenv" -version = "1.2.1" -description = "Read key-value pairs from a .env file and set them as environment variables" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61"}, - {file = "python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6"}, -] - -[package.extras] -cli = ["click (>=5.0)"] - [[package]] name = "python-slugify" version = "8.0.4" @@ -4680,22 +4131,6 @@ mando = ">=0.6,<0.8" [package.extras] toml = ["tomli (>=2.0.1)"] -[[package]] -name = "referencing" -version = "0.37.0" -description = "JSON Referencing + Python" -optional = false -python-versions = ">=3.10" -groups = ["main", "dev"] -files = [ - {file = "referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231"}, - {file = "referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8"}, -] - -[package.dependencies] -attrs = ">=22.2.0" -rpds-py = ">=0.7.0" - [[package]] name = "regex" version = "2024.11.6" @@ -4874,131 +4309,6 @@ pygments = ">=2.13.0,<3.0.0" [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] -[[package]] -name = "rpds-py" -version = "0.30.0" -description = "Python bindings to Rust's persistent data structures (rpds)" -optional = false -python-versions = ">=3.10" -groups = ["main", "dev"] -files = [ - {file = "rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288"}, - {file = "rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221"}, - {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7"}, - {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff"}, - {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7"}, - {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139"}, - {file = "rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464"}, - {file = "rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169"}, - {file = "rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425"}, - {file = "rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d"}, - {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038"}, - {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7"}, - {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed"}, - {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85"}, - {file = "rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c"}, - {file = "rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825"}, - {file = "rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229"}, - {file = "rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad"}, - {file = "rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6"}, - {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51"}, - {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5"}, - {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e"}, - {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394"}, - {file = "rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf"}, - {file = "rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b"}, - {file = "rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e"}, - {file = "rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2"}, - {file = "rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e"}, - {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d"}, - {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7"}, - {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31"}, - {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95"}, - {file = "rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d"}, - {file = "rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15"}, - {file = "rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1"}, - {file = "rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a"}, - {file = "rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9"}, - {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0"}, - {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94"}, - {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08"}, - {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27"}, - {file = "rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6"}, - {file = "rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d"}, - {file = "rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0"}, - {file = "rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07"}, - {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f"}, - {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65"}, - {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f"}, - {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53"}, - {file = "rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed"}, - {file = "rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950"}, - {file = "rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6"}, - {file = "rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb"}, - {file = "rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8"}, - {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5"}, - {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404"}, - {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856"}, - {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40"}, - {file = "rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0"}, - {file = "rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4"}, - {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e"}, - {file = "rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84"}, -] - [[package]] name = "ruff" version = "0.13.1" @@ -5079,18 +4389,6 @@ regex = "2024.11.6" [package.extras] dev = ["black (==24.8.0)", "build (==1.2.2)", "flake8 (==7.2.0)", "mypy (==1.14.0)", "pylint (==3.2.7)", "pytest (==8.3.5)", "pytest-asyncio (==1.1.0)", "tox (==4.26.0)"] -[[package]] -name = "shellingham" -version = "1.5.4" -description = "Tool to Detect Surrounding Shell" -optional = false -python-versions = ">=3.7" -groups = ["main", "dev"] -files = [ - {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, - {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, -] - [[package]] name = "six" version = "1.17.0" @@ -5229,7 +4527,6 @@ files = [ ] [package.dependencies] -greenlet = {version = ">=1", markers = "python_version < \"3.14\" and (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] @@ -5351,113 +4648,6 @@ files = [ {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, ] -[[package]] -name = "tiktoken" -version = "0.12.0" -description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "tiktoken-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3de02f5a491cfd179aec916eddb70331814bd6bf764075d39e21d5862e533970"}, - {file = "tiktoken-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6cfb6d9b7b54d20af21a912bfe63a2727d9cfa8fbda642fd8322c70340aad16"}, - {file = "tiktoken-0.12.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:cde24cdb1b8a08368f709124f15b36ab5524aac5fa830cc3fdce9c03d4fb8030"}, - {file = "tiktoken-0.12.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6de0da39f605992649b9cfa6f84071e3f9ef2cec458d08c5feb1b6f0ff62e134"}, - {file = "tiktoken-0.12.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6faa0534e0eefbcafaccb75927a4a380463a2eaa7e26000f0173b920e98b720a"}, - {file = "tiktoken-0.12.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:82991e04fc860afb933efb63957affc7ad54f83e2216fe7d319007dab1ba5892"}, - {file = "tiktoken-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:6fb2995b487c2e31acf0a9e17647e3b242235a20832642bb7a9d1a181c0c1bb1"}, - {file = "tiktoken-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e227c7f96925003487c33b1b32265fad2fbcec2b7cf4817afb76d416f40f6bb"}, - {file = "tiktoken-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c06cf0fcc24c2cb2adb5e185c7082a82cba29c17575e828518c2f11a01f445aa"}, - {file = "tiktoken-0.12.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f18f249b041851954217e9fd8e5c00b024ab2315ffda5ed77665a05fa91f42dc"}, - {file = "tiktoken-0.12.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:47a5bc270b8c3db00bb46ece01ef34ad050e364b51d406b6f9730b64ac28eded"}, - {file = "tiktoken-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:508fa71810c0efdcd1b898fda574889ee62852989f7c1667414736bcb2b9a4bd"}, - {file = "tiktoken-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1af81a6c44f008cba48494089dd98cccb8b313f55e961a52f5b222d1e507967"}, - {file = "tiktoken-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e68e3e593637b53e56f7237be560f7a394451cb8c11079755e80ae64b9e6def"}, - {file = "tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8"}, - {file = "tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b"}, - {file = "tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37"}, - {file = "tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad"}, - {file = "tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5"}, - {file = "tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3"}, - {file = "tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd"}, - {file = "tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3"}, - {file = "tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160"}, - {file = "tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa"}, - {file = "tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be"}, - {file = "tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a"}, - {file = "tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3"}, - {file = "tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697"}, - {file = "tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16"}, - {file = "tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a"}, - {file = "tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27"}, - {file = "tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb"}, - {file = "tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e"}, - {file = "tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25"}, - {file = "tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f"}, - {file = "tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646"}, - {file = "tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88"}, - {file = "tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff"}, - {file = "tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830"}, - {file = "tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b"}, - {file = "tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b"}, - {file = "tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3"}, - {file = "tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365"}, - {file = "tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e"}, - {file = "tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63"}, - {file = "tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0"}, - {file = "tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a"}, - {file = "tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0"}, - {file = "tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71"}, - {file = "tiktoken-0.12.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:d51d75a5bffbf26f86554d28e78bfb921eae998edc2675650fd04c7e1f0cdc1e"}, - {file = "tiktoken-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:09eb4eae62ae7e4c62364d9ec3a57c62eea707ac9a2b2c5d6bd05de6724ea179"}, - {file = "tiktoken-0.12.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:df37684ace87d10895acb44b7f447d4700349b12197a526da0d4a4149fde074c"}, - {file = "tiktoken-0.12.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:4c9614597ac94bb294544345ad8cf30dac2129c05e2db8dc53e082f355857af7"}, - {file = "tiktoken-0.12.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:20cf97135c9a50de0b157879c3c4accbb29116bcf001283d26e073ff3b345946"}, - {file = "tiktoken-0.12.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:15d875454bbaa3728be39880ddd11a5a2a9e548c29418b41e8fd8a767172b5ec"}, - {file = "tiktoken-0.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cff3688ba3c639ebe816f8d58ffbbb0aa7433e23e08ab1cade5d175fc973fb3"}, - {file = "tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931"}, -] - -[package.dependencies] -regex = ">=2022.1.18" -requests = ">=2.26.0" - -[package.extras] -blobfile = ["blobfile (>=2)"] - -[[package]] -name = "tokenizers" -version = "0.22.1" -description = "" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "tokenizers-0.22.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:59fdb013df17455e5f950b4b834a7b3ee2e0271e6378ccb33aa74d178b513c73"}, - {file = "tokenizers-0.22.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8d4e484f7b0827021ac5f9f71d4794aaef62b979ab7608593da22b1d2e3c4edc"}, - {file = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19d2962dd28bc67c1f205ab180578a78eef89ac60ca7ef7cbe9635a46a56422a"}, - {file = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:38201f15cdb1f8a6843e6563e6e79f4abd053394992b9bbdf5213ea3469b4ae7"}, - {file = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1cbe5454c9a15df1b3443c726063d930c16f047a3cc724b9e6e1a91140e5a21"}, - {file = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7d094ae6312d69cc2a872b54b91b309f4f6fbce871ef28eb27b52a98e4d0214"}, - {file = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afd7594a56656ace95cdd6df4cca2e4059d294c5cfb1679c57824b605556cb2f"}, - {file = "tokenizers-0.22.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ef6063d7a84994129732b47e7915e8710f27f99f3a3260b8a38fc7ccd083f4"}, - {file = "tokenizers-0.22.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ba0a64f450b9ef412c98f6bcd2a50c6df6e2443b560024a09fa6a03189726879"}, - {file = "tokenizers-0.22.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:331d6d149fa9c7d632cde4490fb8bbb12337fa3a0232e77892be656464f4b446"}, - {file = "tokenizers-0.22.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:607989f2ea68a46cb1dfbaf3e3aabdf3f21d8748312dbeb6263d1b3b66c5010a"}, - {file = "tokenizers-0.22.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a0f307d490295717726598ef6fa4f24af9d484809223bbc253b201c740a06390"}, - {file = "tokenizers-0.22.1-cp39-abi3-win32.whl", hash = "sha256:b5120eed1442765cd90b903bb6cfef781fd8fe64e34ccaecbae4c619b7b12a82"}, - {file = "tokenizers-0.22.1-cp39-abi3-win_amd64.whl", hash = "sha256:65fd6e3fb11ca1e78a6a93602490f134d1fdeb13bcef99389d5102ea318ed138"}, - {file = "tokenizers-0.22.1.tar.gz", hash = "sha256:61de6522785310a309b3407bac22d99c4db5dba349935e99e4d15ea2226af2d9"}, -] - -[package.dependencies] -huggingface-hub = ">=0.16.4,<2.0" - -[package.extras] -dev = ["tokenizers[testing]"] -docs = ["setuptools-rust", "sphinx", "sphinx-rtd-theme"] -testing = ["black (==22.3)", "datasets", "numpy", "pytest", "pytest-asyncio", "requests", "ruff"] - [[package]] name = "tomlkit" version = "0.13.3" @@ -5476,7 +4666,7 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] +groups = ["dev"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, @@ -5492,25 +4682,6 @@ notebook = ["ipywidgets (>=6)"] slack = ["slack-sdk"] telegram = ["requests"] -[[package]] -name = "typer-slim" -version = "0.20.0" -description = "Typer, build great CLIs. Easy to code. Based on Python type hints." -optional = false -python-versions = ">=3.8" -groups = ["main", "dev"] -files = [ - {file = "typer_slim-0.20.0-py3-none-any.whl", hash = "sha256:f42a9b7571a12b97dddf364745d29f12221865acef7a2680065f9bb29c7dc89d"}, - {file = "typer_slim-0.20.0.tar.gz", hash = "sha256:9fc6607b3c6c20f5c33ea9590cbeb17848667c51feee27d9e314a579ab07d1a3"}, -] - -[package.dependencies] -click = ">=8.0.0" -typing-extensions = ">=3.7.4.3" - -[package.extras] -standard = ["rich (>=10.11.0)", "shellingham (>=1.3.0)"] - [[package]] name = "types-pyyaml" version = "6.0.12.20250915" @@ -5556,7 +4727,7 @@ version = "0.4.2" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["dev"] files = [ {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, @@ -5683,31 +4854,31 @@ files = [ [[package]] name = "uv" -version = "0.9.6" +version = "0.9.26" description = "An extremely fast Python package and project manager, written in Rust." optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "uv-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:b2f934737c93f88c906b6a47bcc083170210fe5d66565e80a7c139599e5cbf2f"}, - {file = "uv-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a7c6067919d87208c4a6092033c3bc9799cb8be1c8bc6ef419a1f6d42a755329"}, - {file = "uv-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:95a62c1f668272555ad0c446bf44a9924dee06054b831d04c162e0bad736dc28"}, - {file = "uv-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:0169a85d3ba5ef1c37089d64ff26de573439ca84ecf549276a2eee42d7f833f2"}, - {file = "uv-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f0ba311b3ca49d246f36d444d3ee81571619ef95e5f509eb694a81defcbed262"}, - {file = "uv-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e89c964f614fa3f0481060cac709d6da50feac553e1e11227d6c4c81c87af7c"}, - {file = "uv-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ea67369918af24ea7e01991dfc8b8988d1b0b7c49cb39d9e5bc0c409930a0a3f"}, - {file = "uv-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90122a76e6441b8c580fc9faf06bd8c4dbe276cb1c185ad91eceb2afa78e492a"}, - {file = "uv-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86e05782f9b75d39ab1c0af98bf11e87e646a36a61d425021d5b284073e56315"}, - {file = "uv-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c2c2b2b093330e603d838fec26941ab6f62e8d62a012f9fa0d5ed88da39d907"}, - {file = "uv-0.9.6-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:e700b2098f9d365061c572d0729b4e8bc71c6468d83dfaae2537cd66e3cb1b98"}, - {file = "uv-0.9.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:6403176b55388cf94fb8737e73b26ee2a7b1805a9139da5afa951210986d4fcd"}, - {file = "uv-0.9.6-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:62e3f057a9ae5e5003a7cd56b617e940f519f6dabcbb22d36cdd0149df25d409"}, - {file = "uv-0.9.6-py3-none-musllinux_1_1_i686.whl", hash = "sha256:538716ec97f8d899baa7e1c427f4411525459c0ef72ea9b3625ce9610c9976e6"}, - {file = "uv-0.9.6-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:b31377ebf2d0499afc5abe3fe1abded5ca843f3a1161b432fe26eb0ce15bab8e"}, - {file = "uv-0.9.6-py3-none-win32.whl", hash = "sha256:0fde18c22376c8b02954c7db3847bc75ac42619932c44b43f49d056e5cfb05f9"}, - {file = "uv-0.9.6-py3-none-win_amd64.whl", hash = "sha256:d1072db92cc9525febdf9d113c23916dfc20ca03e21218cc7beefe7185a90631"}, - {file = "uv-0.9.6-py3-none-win_arm64.whl", hash = "sha256:166175ba952d2ad727e1dbd57d7cfc1782dfe7b8d79972174a46a7aa33ddceec"}, - {file = "uv-0.9.6.tar.gz", hash = "sha256:547fd27ab5da7cd1a833288a36858852451d416a056825f162ecf2af5be6f8b8"}, + {file = "uv-0.9.26-py3-none-linux_armv6l.whl", hash = "sha256:7dba609e32b7bd13ef81788d580970c6ff3a8874d942755b442cffa8f25dba57"}, + {file = "uv-0.9.26-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b815e3b26eeed00e00f831343daba7a9d99c1506883c189453bb4d215f54faac"}, + {file = "uv-0.9.26-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1b012e6c4dfe767f818cbb6f47d02c207c9b0c82fee69a5de6d26ffb26a3ef3c"}, + {file = "uv-0.9.26-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:ea296b700d7c4c27acdfd23ffaef2b0ecdd0aa1b58d942c62ee87df3b30f06ac"}, + {file = "uv-0.9.26-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:1ba860d2988efc27e9c19f8537a2f9fa499a8b7ebe4afbe2d3d323d72f9aee61"}, + {file = "uv-0.9.26-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8610bdfc282a681a0a40b90495a478599aa3484c12503ef79ef42cd271fd80fe"}, + {file = "uv-0.9.26-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4bf700bd071bd595084b9ee0a8d77c6a0a10ca3773d3771346a2599f306bd9c"}, + {file = "uv-0.9.26-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:89a7beea1c692f76a6f8da13beff3cbb43f7123609e48e03517cc0db5c5de87c"}, + {file = "uv-0.9.26-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:182f5c086c7d03ad447e522b70fa29a0302a70bcfefad4b8cd08496828a0e179"}, + {file = "uv-0.9.26-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d8c62a501f13425b4b0ce1dd4c6b82f3ce5a5179e2549c55f4bb27cc0eb8ef8"}, + {file = "uv-0.9.26-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7e89798bd3df7dcc4b2b4ac4e2fc11d6b3ff4fe7d764aa3012d664c635e2922"}, + {file = "uv-0.9.26-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:60a66f1783ec4efc87b7e1f9bd66e8fd2de3e3b30d122b31cb1487f63a3ea8b7"}, + {file = "uv-0.9.26-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:63c6a1f1187facba1fb45a2fa45396980631a3427ac11b0e3d9aa3ebcf2c73cf"}, + {file = "uv-0.9.26-py3-none-musllinux_1_1_i686.whl", hash = "sha256:c6d8650fbc980ccb348b168266143a9bd4deebc86437537caaf8ff2a39b6ea50"}, + {file = "uv-0.9.26-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:25278f9298aa4dade38241a93d036739b0c87278dcfad1ec1f57e803536bfc49"}, + {file = "uv-0.9.26-py3-none-win32.whl", hash = "sha256:10d075e0193e3a0e6c54f830731c4cb965d6f4e11956e84a7bed7ed61d42aa27"}, + {file = "uv-0.9.26-py3-none-win_amd64.whl", hash = "sha256:0315fc321f5644b12118f9928086513363ed9b29d74d99f1539fda1b6b5478ab"}, + {file = "uv-0.9.26-py3-none-win_arm64.whl", hash = "sha256:344ff38749b6cd7b7dfdfb382536f168cafe917ae3a5aa78b7a63746ba2a905b"}, + {file = "uv-0.9.26.tar.gz", hash = "sha256:8b7017a01cc48847a7ae26733383a2456dd060fc50d21d58de5ee14f6b6984d7"}, ] [[package]] @@ -5745,14 +4916,14 @@ files = [ [[package]] name = "voluptuous-openapi" -version = "0.1.0" +version = "0.2.0" description = "Convert voluptuous schemas to OpenAPI Schema object" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "voluptuous_openapi-0.1.0-py3-none-any.whl", hash = "sha256:c3aac740286d368c90a99e007d55ddca7fcddf790d218c60ee0eeec2fcd3db2b"}, - {file = "voluptuous_openapi-0.1.0.tar.gz", hash = "sha256:84bc44107c472ba8782f7a4cb342d19d155d5fe7f92367f092cd96cc850ff1b7"}, + {file = "voluptuous_openapi-0.2.0-py3-none-any.whl", hash = "sha256:d51f07be8af44b11570b7366785d90daa716b7fd11ea2845803763ae551f35cf"}, + {file = "voluptuous_openapi-0.2.0.tar.gz", hash = "sha256:2366be934c37bb5fd8ed6bd5a2a46b1079b57dfbdf8c6c02e88f4ca13e975073"}, ] [package.dependencies] @@ -6296,27 +5467,7 @@ files = [ [package.dependencies] ifaddr = ">=0.1.7" -[[package]] -name = "zipp" -version = "3.23.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, - {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] -type = ["pytest-mypy"] - [metadata] lock-version = "2.1" -python-versions = ">=3.13.2,<3.14" -content-hash = "a8310fcfa90db01e85d8b5bd8dc3327f67f97996e16d49e4d978e7c877471325" +python-versions = ">=3.14.2,<3.15" +content-hash = "2da71daf3ee86f072b5308ca5427cd1e17f2e3a20f432f3d154d2ae82e4361ec" diff --git a/pyproject.toml b/pyproject.toml index 0017e18..c392a20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,13 +9,14 @@ readme = "README.md" package-mode = false [tool.poetry.dependencies] -python = ">=3.13.2,<3.14" -homeassistant = "2025.12.4,<2026.0.0" # Pin to exact version for custom component compatibility +python = ">=3.14.2,<3.15" +homeassistant = "2026.2.2" +mashumaro = ">=3.17.0" # HA core package_constraints.txt minimum; poetry under-resolves without this span-panel-api = {path = "../span-panel-api", develop = true, extras = ["grpc"]} [tool.poetry.group.dev.dependencies] # Type stubs and dev-only tools that don't conflict with HA runtime -homeassistant-stubs = "2025.12.4" +homeassistant-stubs = "2026.2.2" types-requests = "*" types-PyYAML = "*" mypy = "*" @@ -29,7 +30,7 @@ prettier = "*" radon = "*" pylint = "*" # For import-outside-toplevel checks # Testing dependencies -pytest = "^9.0.0" # Compatible with Python 3.13 +pytest = "^9.0.0" # Compatible with Python 3.14 pytest-homeassistant-custom-component = "^0.13.300" # Latest version compatible with HA 2025.8.2 isort = "*" @@ -119,7 +120,7 @@ extraPaths = [ "../ha-synthetic-sensors/src" ] pythonPlatform = "Darwin" -pythonVersion = "3.13" +pythonVersion = "3.14" typeCheckingMode = "basic" useLibraryCodeForTypes = true autoImportCompletions = true From 943e9fdfef61bac22482df9ba0216fe8836f41d3 Mon Sep 17 00:00:00 2001 From: cayossarian Date: Thu, 19 Feb 2026 16:50:00 -0800 Subject: [PATCH 12/13] chore: sync branch with main version alignment - Update .python-version from 3.13.2 to 3.14.2 (already on main) - Tighten pytest-homeassistant-custom-component to ^0.13.315 (matches main) - Regenerate poetry.lock --- .python-version | 2 +- poetry.lock | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.python-version b/.python-version index 3e388a4..95ed564 100644 --- a/.python-version +++ b/.python-version @@ -1 +1 @@ -3.13.2 +3.14.2 diff --git a/poetry.lock b/poetry.lock index fa978ff..6883900 100644 --- a/poetry.lock +++ b/poetry.lock @@ -5470,4 +5470,4 @@ ifaddr = ">=0.1.7" [metadata] lock-version = "2.1" python-versions = ">=3.14.2,<3.15" -content-hash = "2da71daf3ee86f072b5308ca5427cd1e17f2e3a20f432f3d154d2ae82e4361ec" +content-hash = "60694999bf98989b44d8be066ad8da2d2f89fef874ef3c0dfa23b7e019229f61" diff --git a/pyproject.toml b/pyproject.toml index c392a20..50ae080 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ radon = "*" pylint = "*" # For import-outside-toplevel checks # Testing dependencies pytest = "^9.0.0" # Compatible with Python 3.14 -pytest-homeassistant-custom-component = "^0.13.300" # Latest version compatible with HA 2025.8.2 +pytest-homeassistant-custom-component = "^0.13.315" # Latest version compatible with HA 2026.2.x isort = "*" [build-system] From d1c52f5950d783e73149537da8945c289539e958 Mon Sep 17 00:00:00 2001 From: cayossarian Date: Thu, 19 Feb 2026 17:06:26 -0800 Subject: [PATCH 13/13] docs: add Gen3 panel support documentation - Update integration description to note Gen2 (REST/OpenAPI) and Gen3 (gRPC) support - Annotate feature list with Gen2/Gen3 availability - Update installation steps and authorization section for Gen3 (no auth required) - Replace outdated Limitations section with Panel Generation Support section covering Gen3 read-only constraints, missing features, and Gen3-exclusive metrics - Add Gen2 vs Gen3 feature comparison table --- README.md | 85 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 3761503..a401d32 100644 --- a/README.md +++ b/README.md @@ -14,19 +14,15 @@ monitoring and control of your home's electrical system. [![prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit) -This integration relies on the OpenAPI interface contract sourced from the SPAN Panel. The integration may break if SPAN changes the API in an incompatible way. +This integration supports both **Gen2** panels (MAIN 32 — REST/OpenAPI) and **Gen3** panels (MAIN 40 / MLO 48 — gRPC). +The Gen2 integration relies on the OpenAPI interface contract sourced from the panel. The integration may break if SPAN +changes the API in an incompatible way. The software is provided as-is with no warranty or guarantee of performance or suitability to your particular setting. This integration provides the user with sensors and controls that are useful in understanding an installation's power consumption, energy usage, and the ability to control user-manageable panel circuits. -## Major Upgrade - -**Before upgrading to version 1.2.x from a prior version, please backup your Home Assistant configuration and database.** - -See the [CHANGELOG.md](CHANGELOG.md) for detailed information about all new features and improvements. - ## HACS Upgrade Process When upgrading through HACS, you'll see a notification about the new version. Before clicking "Update": @@ -55,21 +51,21 @@ If you encounter any issues during the upgrade, you can: This integration provides a Home Assistant device for your SPAN panel with entities for: -- User Managed Circuits +- User Managed Circuits _(Gen2 only)_ - On/Off Switch (user managed circuits) - Priority Selector (user managed circuits) - Power Sensors - - Power Usage / Generation (Watts) - - Energy Usage / Generation (Wh) - - Net Energy (Wh) - Calculated as consumed energy minus produced energy -- Panel and Grid Status + - Power Usage / Generation (Watts) _(Gen2 and Gen3)_ + - Energy Usage / Generation (Wh) _(Gen2 only)_ + - Net Energy (Wh) - Calculated as consumed energy minus produced energy _(Gen2 only)_ +- Panel and Grid Status _(Gen2 only)_ - Main Relay State (e.g., CLOSED) - Current Run Config (e.g., PANEL_ON_GRID) - DSM State (e.g., DSM_GRID_UP) - DSM Grid State (e.g., DSM_ON_GRID) - Network Connectivity Status (Wi-Fi, Wired, & Cellular) - Door State (device class is tamper) -- Storage Battery +- Storage Battery _(Gen2 only)_ - Battery percentage (options configuration) ## Installation @@ -85,20 +81,23 @@ This integration provides a Home Assistant device for your SPAN panel with entit 9. Click `+ Add Integration`. 10. Search for "Span". This entry should correspond to this repository and offer the current version. 11. Enter the IP of your SPAN Panel to begin setup, or select the automatically discovered panel if it shows up or another address if you have multiple panels. -12. Use the door proximity authentication (see below) and optionally create a token for future configurations. Obtaining a token **_may_** be more durable - against network changes, for example, if you change client hostname or IP and don't want to access the panel for authorization. +12. **Gen2 panels**: Use the door proximity authentication (see below) and optionally create a token for future configurations. Obtaining a token **_may_** be + more durable against network changes, for example, if you change client hostname or IP and don't want to access the panel for authorization. + **Gen3 panels** (MAIN 40 / MLO 48): No authentication is required. The integration connects directly over gRPC without any token or door-proximity step. 13. See post install steps for solar or scan frequency configuration to optionally add additional sensors if applicable. ## Authorization Methods -### Method 1: Door Proximity Authentication +> **Gen3 panels (MAIN 40 / MLO 48) do not require authentication.** Steps 1 and 2 below apply only to Gen2 (MAIN 32) panels. + +### Method 1: Door Proximity Authentication (Gen2 only) 1. Open your SPAN Panel door 2. Press the door sensor button at the top 3 times in succession 3. Wait for the frame lights to blink, indicating the panel is "unlocked" for 15 minutes 4. Complete the integration setup in Home Assistant -### Method 2: Authentication Token (Optional) +### Method 2: Authentication Token (Gen2 only, optional) To acquire an authorization token, proceed as follows while the panel is in its unlocked period: @@ -207,14 +206,46 @@ You can change the display precision for any entity in Home Assistant via `Setti to change in the list and click on it, then click on the gear wheel in the top right. Select the precision you prefer from the "Display Precision" menu and then press `UPDATE`. -## Limitations +## Panel Generation Support + +### Gen2 — SPAN Panel MAIN 32 + +The original MAIN 32 uses a REST/OpenAPI interface and provides the full feature set of this integration, including circuit relay control, circuit priority +selector, energy history, battery/storage state-of-energy, solar/DSM state, and network connectivity status. + +### Gen3 — SPAN Panel MAIN 40 / MLO 48 -The original SPAN Panel MAIN 32 has a standardized OpenAPI endpoint that is leveraged by this integration. +The MAIN 40 and MLO 48 released in Q2 2025 use a gRPC-based interface on port 50065. Gen3 panels are **read-only** — no authentication is required to +connect, but the protocol does not expose any write operations. As a result, the following features are **not available** on Gen3 panels: -However, the new SPAN Panel MAIN 40 and MLO 48 that were released in Q2 of 2025 leverage a different hardware/software stack, even going so far as to use a -different mobile app logins. This stack is not yet publicly documented and as such, we have not had a chance to discern how to support this stack at the time of -writing this. The underlying software may be the same codebase as the MAIN 32, so in theory, SPAN may provide access that we have yet to discover or that they -will eventually expose. +- Circuit relay (on/off) control — no switches +- Circuit priority selector +- Energy history (Wh accumulated) +- Battery / storage state-of-energy +- Solar / DSM state +- Network connectivity status + +Gen3 panels **do** provide real-time power metrics (watts, voltage, current) for each circuit, as well as apparent power (VA), reactive power (VAR), and +power factor — fields that are not available on Gen2. + +The integration auto-detects the panel generation on first connection. No additional configuration is required to support a Gen3 panel; entities that are not +available for Gen3 will simply not be created. + +### Feature Comparison + +| Feature | Gen2 (MAIN 32) | Gen3 (MAIN 40 / MLO 48) | +| --------------------------------- | -------------- | ----------------------- | +| Authentication | Required (JWT) | None | +| Circuit on/off switch | Yes | No | +| Circuit priority selector | Yes | No | +| Energy history (Wh) | Yes | No | +| Battery / storage SOE | Yes | No | +| Solar / DSM state | Yes | No | +| Network connectivity status | Yes | No | +| Real-time power (W) per circuit | Yes | Yes | +| Apparent power (VA) per circuit | No | Yes | +| Reactive power (VAR) per circuit | No | Yes | +| Power factor per circuit | No | Yes | ## Troubleshooting @@ -302,7 +333,7 @@ This integration is published under the MIT license. This repository is set up as part of an organization so a single committer is not the weak link. The repository is a fork in a long line of SPAN forks that may or may not be stable (from newer to older): -- SpanPanel/span (current GitHub organization, current repository, currently listed in HACS) +- SpanPanel/span (current GitHub organization, current repository, default listed in HACS) - SpanPanel/Span (was moved to [SpanPanel/SpanCustom](https://github.com/SpanPanel/SpanCustom)) - cayossarian/span - haext/span @@ -311,12 +342,6 @@ or may not be stable (from newer to older): - wez/span-hacs - galak/span-hacs -Additional contributors: - -- pavandave -- sargonas -- NickBorgersOnLowSecurityNode - ## Issues If you have a problem with the integration, feel free to [open an issue](https://github.com/SpanPanel/span/issues), but please know that issues regarding your