mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-10-19 04:11:57 +00:00
fix: unify mypy environments for local checks and CI (#1291)
The isolated pre-commit mypy hook previously omitted runtime type information that make mypy used, hiding errors involving dependencies such as Pydantic and Pendulum. Makefile, pre-commit and CI now run the same full-project typing policy in the development environment defined by uv.lock. - Use uv run --locked --exact --extra dev and the same mypy arguments for Makefile and the local hook. Check all of src and tests, including on configuration-only changes. - Pin Python 3.13 for local development and the pre-commit CI job, and install the locked pre-commit version in CI. - Disable incremental analysis because existing Pendulum cache state changes mypy 2.3.1 diagnostics. Document the policy, the performance tradeoff and the existing typing debt. - Add a regression test that exercises Makefile, the hook and the CI command in a temporary project, accepting valid dependency types and detecting deliberate Pydantic/Pendulum assignment errors. Resolve the newly detected mypy diagnostics. - Enable the numpydantic and Pydantic mypy plugins, retaining strict Pydantic constructor typing with init_typed = true. Validate raw/coercible payloads through model_validate. - Propagate concrete record, provider and time-window types through generic collections, factories and lookup methods. Preserve runtime field inspection and generated time-window documentation. - Align Pendulum annotations with actual factory/arithmetic results while retaining Pydantic validation adapters at runtime. Correct optional values, array boundaries, REST handlers and plotting interfaces. - Add pinned scipy-stubs and types-psutil, update uv.lock, and supply the plugins' dependencies. - Add runtime regression coverage for validated path defaults, normalized time-series metadata, generic field inspection, invalid timestamps and unsupported provider imports. Runtime and compatibility details: - Validate path defaults as Path objects while retaining raw string defaults needed by migration serialization with exclude_defaults. - Normalize feed-in tariff lists and default charge rates to NumPy arrays; reject missing timestamps/uninitialized values explicitly. Importing into a provider without import support returns HTTP 400. - Public JSON schemas and OpenAPI structure match main (excluding the generated version). Signed-off-by: dr-dimitry Signed-off-by: dr-dimitry Signed-off-by: Bobby Noelte <b0661n0e17e@gmail.com> Co-authored-by: dr-dimitri <87113560+dr-dimitri@users.noreply.github.com> Co-authored-by: Normann <github@koldrack.com>
This commit is contained in:
co-authored by
dr-dimitri
Normann
parent
5b584cbb57
commit
1abdd345c4
@@ -69,7 +69,7 @@ def adapter_providers() -> list[Union["HomeAssistantAdapter", "NodeREDAdapter"]]
|
||||
]
|
||||
|
||||
|
||||
class Adapter(AdapterContainer):
|
||||
class Adapter(AdapterContainer[HomeAssistantAdapter | NodeREDAdapter]):
|
||||
"""Adapter container to manage multiple adapter providers."""
|
||||
|
||||
providers: list[
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
from abc import abstractmethod
|
||||
from typing import Any, Optional
|
||||
from typing import Any, Generic, Optional, TypeVar
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import (
|
||||
@@ -102,18 +102,21 @@ class AdapterProvider(SingletonMixin, ConfigMixin, MeasurementMixin, StartMixin,
|
||||
await self._update_data()
|
||||
|
||||
|
||||
class AdapterContainer(SingletonMixin, ConfigMixin, PydanticBaseModel):
|
||||
AdapterProviderT = TypeVar("AdapterProviderT", bound=AdapterProvider)
|
||||
|
||||
|
||||
class AdapterContainer(SingletonMixin, ConfigMixin, PydanticBaseModel, Generic[AdapterProviderT]):
|
||||
"""A container for managing multiple adapter provider instances.
|
||||
|
||||
This class enables to control multiple adapter providers
|
||||
"""
|
||||
|
||||
providers: list[AdapterProvider] = Field(
|
||||
providers: list[AdapterProviderT] = Field(
|
||||
default_factory=list, json_schema_extra={"description": "List of adapter providers"}
|
||||
)
|
||||
|
||||
@field_validator("providers")
|
||||
def check_providers(cls, value: list[AdapterProvider]) -> list[AdapterProvider]:
|
||||
def check_providers(cls, value: list[AdapterProviderT]) -> list[AdapterProviderT]:
|
||||
# Check each item in the list
|
||||
for item in value:
|
||||
if not isinstance(item, AdapterProvider):
|
||||
@@ -149,7 +152,7 @@ class AdapterContainer(SingletonMixin, ConfigMixin, PydanticBaseModel):
|
||||
return
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def provider_by_id(self, provider_id: str) -> AdapterProvider:
|
||||
def provider_by_id(self, provider_id: str) -> AdapterProviderT:
|
||||
"""Retrieves an adapter provider by its unique identifier.
|
||||
|
||||
This method searches through the list of all available providers and
|
||||
|
||||
@@ -145,7 +145,13 @@ class HomeAssistantAdapterCommonSettings(SettingsBaseModel):
|
||||
"""Entity IDs available at Home Assistant."""
|
||||
try:
|
||||
adapter_eos = get_adapter()
|
||||
result = adapter_eos.provider_by_id("HomeAssistant").get_homeassistant_entity_ids()
|
||||
provider = adapter_eos.provider_by_id("HomeAssistant")
|
||||
except Exception:
|
||||
return []
|
||||
if not isinstance(provider, HomeAssistantAdapter):
|
||||
raise TypeError("HomeAssistant provider must be a HomeAssistantAdapter")
|
||||
try:
|
||||
result = provider.get_homeassistant_entity_ids()
|
||||
except Exception:
|
||||
return []
|
||||
return result
|
||||
@@ -156,7 +162,13 @@ class HomeAssistantAdapterCommonSettings(SettingsBaseModel):
|
||||
"""Entity IDs for optimization solution available at EOS."""
|
||||
try:
|
||||
adapter_eos = get_adapter()
|
||||
result = adapter_eos.provider_by_id("HomeAssistant").get_eos_solution_entity_ids()
|
||||
provider = adapter_eos.provider_by_id("HomeAssistant")
|
||||
except Exception:
|
||||
return []
|
||||
if not isinstance(provider, HomeAssistantAdapter):
|
||||
raise TypeError("HomeAssistant provider must be a HomeAssistantAdapter")
|
||||
try:
|
||||
result = provider.get_eos_solution_entity_ids()
|
||||
except Exception:
|
||||
return []
|
||||
return result
|
||||
@@ -167,9 +179,13 @@ class HomeAssistantAdapterCommonSettings(SettingsBaseModel):
|
||||
"""Entity IDs for energy management instructions available at EOS."""
|
||||
try:
|
||||
adapter_eos = get_adapter()
|
||||
result = adapter_eos.provider_by_id(
|
||||
"HomeAssistant"
|
||||
).get_eos_device_instruction_entity_ids()
|
||||
provider = adapter_eos.provider_by_id("HomeAssistant")
|
||||
except Exception:
|
||||
return []
|
||||
if not isinstance(provider, HomeAssistantAdapter):
|
||||
raise TypeError("HomeAssistant provider must be a HomeAssistantAdapter")
|
||||
try:
|
||||
result = provider.get_eos_device_instruction_entity_ids()
|
||||
except Exception:
|
||||
return []
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user