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
+17
-17
@@ -7,7 +7,7 @@ including edge cases, error handling, and timezone behavior.
|
||||
import datetime
|
||||
import json
|
||||
import re
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import babel
|
||||
@@ -621,7 +621,7 @@ class TestToTime:
|
||||
def test_to_time_invalid_input_type(self):
|
||||
"""Test to_time with invalid input type."""
|
||||
with pytest.raises(ValueError, match="Unsupported type"):
|
||||
to_time({"invalid": "input"})
|
||||
to_time(cast(Any, {"invalid": "input"}))
|
||||
|
||||
def test_to_time_invalid_hour_integer(self):
|
||||
"""Test to_time with invalid hour as integer."""
|
||||
@@ -657,7 +657,7 @@ class TestToTime:
|
||||
def test_to_time_invalid_timezone_type(self):
|
||||
"""Test to_time with invalid timezone type."""
|
||||
with pytest.raises(ValueError, match="Invalid timezone"):
|
||||
to_time("14:30", in_timezone=123)
|
||||
to_time("14:30", in_timezone=cast(Any, 123))
|
||||
|
||||
def test_to_time_microseconds_precision(self):
|
||||
"""Test to_time preserves microsecond precision."""
|
||||
@@ -727,7 +727,7 @@ class TestTimeUtilityIntegration:
|
||||
test_time: Time
|
||||
|
||||
# Test with string input
|
||||
model = TestModel(test_time="14:30:45")
|
||||
model = TestModel.model_validate(dict(test_time="14:30:45"))
|
||||
assert isinstance(model.test_time, Time)
|
||||
assert model.test_time.hour == 14
|
||||
|
||||
@@ -748,8 +748,8 @@ class TestTimeUtilityIntegration:
|
||||
|
||||
for case in test_cases:
|
||||
# Both should produce the same result
|
||||
direct_result = to_time(case)
|
||||
model_result = TestModel(test_time=case).test_time
|
||||
direct_result = to_time(cast(Any, case))
|
||||
model_result = TestModel.model_validate(dict(test_time=case)).test_time
|
||||
|
||||
assert direct_result.hour == model_result.hour
|
||||
assert direct_result.minute == model_result.minute
|
||||
@@ -770,12 +770,12 @@ class ScheduleModel(PydanticBaseModel):
|
||||
class TestPendulumTypes:
|
||||
|
||||
def test_valid_schedule_model(self):
|
||||
model = ScheduleModel(
|
||||
model = ScheduleModel.model_validate(dict(
|
||||
start_time="14:30:00",
|
||||
run_duration=to_duration("PT2H"),
|
||||
scheduled_at=to_datetime("2025-07-04T09:00:00+02:00"),
|
||||
run_on=to_datetime("2025-07-04")
|
||||
)
|
||||
))
|
||||
|
||||
assert isinstance(model.start_time, pendulum.Time)
|
||||
assert isinstance(model.run_duration, pendulum.Duration)
|
||||
@@ -788,12 +788,12 @@ class TestPendulumTypes:
|
||||
assert model.run_on.to_date_string() == "2025-07-04"
|
||||
|
||||
def test_json_serialization(self):
|
||||
model = ScheduleModel(
|
||||
model = ScheduleModel.model_validate(dict(
|
||||
start_time=pendulum.time(6, 15),
|
||||
run_duration=pendulum.duration(minutes=45),
|
||||
scheduled_at=pendulum.datetime(2025, 7, 4, 6, 15, tz="Europe/Berlin"),
|
||||
run_on=pendulum.date(2025, 7, 4)
|
||||
)
|
||||
))
|
||||
|
||||
json_data = model.model_dump(mode="json")
|
||||
assert "06:15:00" in json_data["start_time"]
|
||||
@@ -809,30 +809,30 @@ class TestPendulumTypes:
|
||||
|
||||
def test_invalid_start_time(self):
|
||||
with pytest.raises(ValidationError):
|
||||
ScheduleModel(
|
||||
ScheduleModel.model_validate(dict(
|
||||
start_time="invalid",
|
||||
run_duration="PT1H",
|
||||
scheduled_at="2025-07-04T09:00:00+02:00",
|
||||
run_on="2025-07-04"
|
||||
)
|
||||
))
|
||||
|
||||
def test_invalid_duration(self):
|
||||
with pytest.raises(ValidationError):
|
||||
ScheduleModel(
|
||||
ScheduleModel.model_validate(dict(
|
||||
start_time="10:00:00",
|
||||
run_duration="2 hours", # invalid ISO 8601 duration
|
||||
scheduled_at="2025-07-04T09:00:00+02:00",
|
||||
run_on="2025-07-04"
|
||||
)
|
||||
))
|
||||
|
||||
def test_type_coercion(self):
|
||||
dt = pendulum.datetime(2025, 7, 4, 12, 0)
|
||||
model = ScheduleModel(
|
||||
model = ScheduleModel.model_validate(dict(
|
||||
start_time=pendulum.time(12, 0),
|
||||
run_duration=pendulum.duration(hours=3),
|
||||
scheduled_at=dt,
|
||||
run_on=dt.date()
|
||||
)
|
||||
))
|
||||
assert model.scheduled_at.hour == 12
|
||||
assert model.run_duration.total_minutes() == 180
|
||||
|
||||
@@ -1424,7 +1424,7 @@ def test_hours_in_day(set_other_timezone, local_timezone, date, in_timezone, exp
|
||||
"""Test the `test_hours_in_day` function."""
|
||||
set_other_timezone(local_timezone)
|
||||
date_input = to_datetime(date, in_timezone=in_timezone)
|
||||
assert date_input.timezone.name == in_timezone
|
||||
assert date_input.timezone_name == in_timezone
|
||||
assert hours_in_day(date_input) == expected_hours
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user