mirror of
https://github.com/Akkudoktor-EOS/EOS.git
synced 2026-09-11 10:26:38 +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
+16
-13
@@ -81,7 +81,7 @@ class TestMergeModels:
|
||||
|
||||
def test_flat_override(self):
|
||||
"""Top-level fields in update_dict override those in source, including None."""
|
||||
source = SampleModel(name="Test", count=10, config={"threshold": 5})
|
||||
source = SampleModel.model_validate(dict(name="Test", count=10, config={"threshold": 5}))
|
||||
update = {"name": "Updated"}
|
||||
result = merge_models(source, update)
|
||||
|
||||
@@ -91,7 +91,7 @@ class TestMergeModels:
|
||||
|
||||
def test_flat_override_with_none(self):
|
||||
"""Update with None value should override source value."""
|
||||
source = SampleModel(name="Test", count=10, config={"threshold": 5}, optional="keep me")
|
||||
source = SampleModel.model_validate(dict(name="Test", count=10, config={"threshold": 5}, optional="keep me"))
|
||||
update = {"optional": None}
|
||||
result = merge_models(source, update)
|
||||
|
||||
@@ -99,7 +99,7 @@ class TestMergeModels:
|
||||
|
||||
def test_nested_override(self):
|
||||
"""Nested fields in update_dict override nested fields in source, including None."""
|
||||
source = SampleModel(name="Test", count=10, config={"threshold": 5, "enabled": True})
|
||||
source = SampleModel.model_validate(dict(name="Test", count=10, config={"threshold": 5, "enabled": True}))
|
||||
update = {"config": {"threshold": 99, "enabled": False}}
|
||||
result = merge_models(source, update)
|
||||
|
||||
@@ -108,7 +108,7 @@ class TestMergeModels:
|
||||
|
||||
def test_nested_override_with_none(self):
|
||||
"""Nested update with None should override nested source values."""
|
||||
source = SampleModel(name="Test", count=10, config={"threshold": 5, "enabled": True})
|
||||
source = SampleModel.model_validate(dict(name="Test", count=10, config={"threshold": 5, "enabled": True}))
|
||||
update = {"config": {"threshold": None}}
|
||||
result = merge_models(source, update)
|
||||
|
||||
@@ -117,7 +117,7 @@ class TestMergeModels:
|
||||
|
||||
def test_preserve_source_values(self):
|
||||
"""Source values are preserved if not overridden in update_dict."""
|
||||
source = SampleModel(name="Source", count=7, config={"threshold": 1})
|
||||
source = SampleModel.model_validate(dict(name="Source", count=7, config={"threshold": 1}))
|
||||
update: dict[str, Any] = {}
|
||||
result = merge_models(source, update)
|
||||
|
||||
@@ -127,7 +127,7 @@ class TestMergeModels:
|
||||
|
||||
def test_update_extends_source(self):
|
||||
"""Optional fields in update_dict are added to result."""
|
||||
source = SampleModel(name="Test", count=10, config={"threshold": 5})
|
||||
source = SampleModel.model_validate(dict(name="Test", count=10, config={"threshold": 5}))
|
||||
update = {"optional": "new value"}
|
||||
result = merge_models(source, update)
|
||||
|
||||
@@ -135,7 +135,7 @@ class TestMergeModels:
|
||||
|
||||
def test_update_extends_source_with_none(self):
|
||||
"""Optional field with None in update_dict is added and overrides source."""
|
||||
source = SampleModel(name="Test", count=10, config={"threshold": 5}, optional="value")
|
||||
source = SampleModel.model_validate(dict(name="Test", count=10, config={"threshold": 5}, optional="value"))
|
||||
update = {"optional": None}
|
||||
result = merge_models(source, update)
|
||||
|
||||
@@ -143,7 +143,7 @@ class TestMergeModels:
|
||||
|
||||
def test_deep_merge_behavior(self):
|
||||
"""Nested updates merge with source, overriding only specified subkeys."""
|
||||
source = SampleModel(name="Model", count=3, config={"threshold": 1, "enabled": False})
|
||||
source = SampleModel.model_validate(dict(name="Model", count=3, config={"threshold": 1, "enabled": False}))
|
||||
update = {"config": {"enabled": True}}
|
||||
result = merge_models(source, update)
|
||||
|
||||
@@ -152,7 +152,7 @@ class TestMergeModels:
|
||||
|
||||
def test_override_all(self):
|
||||
"""All fields in update_dict override all fields in source, including None."""
|
||||
source = SampleModel(name="Orig", count=1, config={"threshold": 10, "enabled": True})
|
||||
source = SampleModel.model_validate(dict(name="Orig", count=1, config={"threshold": 10, "enabled": True}))
|
||||
update = {
|
||||
"name": "New",
|
||||
"count": None,
|
||||
@@ -376,7 +376,7 @@ class TestPydanticBaseModel:
|
||||
|
||||
def test_invalid_datetime_string(self):
|
||||
with pytest.raises(ValueError):
|
||||
PydanticTestModel(datetime_field="invalid_datetime")
|
||||
PydanticTestModel.model_validate(dict(datetime_field="invalid_datetime"))
|
||||
|
||||
def test_iso8601_serialization(self):
|
||||
dt = pendulum.datetime(2024, 12, 21, 15, 0, 0)
|
||||
@@ -444,8 +444,11 @@ class TestPydanticDateTimeData:
|
||||
"timestamps": ["2024-12-21T15:00:00+00:00"],
|
||||
"values": [100],
|
||||
}
|
||||
model = PydanticDateTimeData(root=data)
|
||||
assert pendulum.parse(model.root["timestamps"][0]) == pendulum.parse(
|
||||
model = PydanticDateTimeData.model_validate(data)
|
||||
timestamps = model.root["timestamps"]
|
||||
assert isinstance(timestamps, list)
|
||||
assert isinstance(timestamps[0], str)
|
||||
assert pendulum.parse(timestamps[0]) == pendulum.parse(
|
||||
"2024-12-21T15:00:00+00:00"
|
||||
)
|
||||
|
||||
@@ -457,7 +460,7 @@ class TestPydanticDateTimeData:
|
||||
with pytest.raises(
|
||||
ValidationError, match="All lists in the dictionary must have the same length"
|
||||
):
|
||||
PydanticDateTimeData(root=data)
|
||||
PydanticDateTimeData.model_validate(data)
|
||||
|
||||
|
||||
class TestPydanticDateTimeDataFrame:
|
||||
|
||||
Reference in New Issue
Block a user