* Mypy: Initial support

 * Add to pre-commit (currently installs own deps, could maybe changed
   to poetry venv in the future to reuse environment and don't need
   duplicated types deps).
 * Add type hints.

* Mypy: Add missing annotations
This commit is contained in:
Dominique Lasserre
2024-11-26 22:28:05 +01:00
committed by GitHub
parent 595b73359c
commit 6e52c9bef2
31 changed files with 637 additions and 531 deletions

View File

@@ -15,7 +15,7 @@ from akkudoktoreos.config import EOS_DIR, AppConfig, load_config
def load_config_tmp(tmp_path: Path) -> AppConfig:
"""Creates an AppConfig from default.config.json with a tmp output directory."""
config = load_config(tmp_path)
config.directories.output = tmp_path
config.directories.output = str(tmp_path)
return config

View File

@@ -299,7 +299,7 @@ def test_cache_in_file_decorator_forces_update(cache_store):
cache_file.write(result2)
# Call the decorated function again with force update (should get result from function)
result = my_function(until_date=until_date, force_update=True)
result = my_function(until_date=until_date, force_update=True) # type: ignore[call-arg]
assert result == result1
# Assure result was written to the same cache file
@@ -319,7 +319,7 @@ def test_cache_in_file_handles_ttl(cache_store):
return "New result"
# Call the decorated function
result = my_function(with_ttl="1 second")
result = my_function(with_ttl="1 second") # type: ignore[call-arg]
# Overwrite cache file
key = next(iter(cache_store._store))
@@ -330,14 +330,14 @@ def test_cache_in_file_handles_ttl(cache_store):
cache_file.seek(0) # Move to the start of the file
assert cache_file.read() == "Modified result"
result = my_function(with_ttl="1 second")
result = my_function(with_ttl="1 second") # type: ignore[call-arg]
assert result == "Modified result"
# Wait one second to let the cache time out
sleep(1)
# Call again - cache should be timed out
result = my_function(with_ttl="1 second")
result = my_function(with_ttl="1 second") # type: ignore[call-arg]
assert result == "New result"
@@ -349,7 +349,7 @@ def test_cache_in_file_handles_bytes_return(cache_store):
# Define a function that returns bytes
@cache_in_file()
def my_function(until_date=None):
def my_function(until_date=None) -> bytes:
return b"Some binary data"
# Call the decorated function
@@ -358,7 +358,14 @@ def test_cache_in_file_handles_bytes_return(cache_store):
# Check if the binary data was written to the cache file
key = next(iter(cache_store._store))
cache_file = cache_store._store[key][0]
assert len(cache_store._store) == 1
assert cache_file is not None
cache_file.seek(0)
result1 = pickle.load(cache_file)
assert result1 == result
# Access cache
result = my_function(until_date=datetime.now() + timedelta(days=1))
assert len(cache_store._store) == 1
assert cache_store._store[key][0] is not None
assert result1 == result

View File

@@ -8,6 +8,7 @@ from akkudoktoreos.devices.inverter import Wechselrichter, WechselrichterParamet
from akkudoktoreos.prediction.ems import (
EnergieManagementSystem,
EnergieManagementSystemParameters,
SimulationResult,
)
prediction_hours = 48
@@ -211,9 +212,9 @@ def create_ems_instance(tmp_config: AppConfig) -> EnergieManagementSystem:
preis_euro_pro_wh_akku=preis_euro_pro_wh_akku,
gesamtlast=gesamtlast,
),
wechselrichter=wechselrichter,
eauto=eauto,
home_appliance=home_appliance,
wechselrichter=wechselrichter,
)
return ems
@@ -255,26 +256,7 @@ def test_simulation(create_ems_instance):
# Check that the result is a dictionary
assert isinstance(result, dict), "Result should be a dictionary."
# Verify that the expected keys are present in the result
expected_keys = [
"Last_Wh_pro_Stunde",
"Netzeinspeisung_Wh_pro_Stunde",
"Netzbezug_Wh_pro_Stunde",
"Kosten_Euro_pro_Stunde",
"akku_soc_pro_stunde",
"Einnahmen_Euro_pro_Stunde",
"Gesamtbilanz_Euro",
"EAuto_SoC_pro_Stunde",
"Gesamteinnahmen_Euro",
"Gesamtkosten_Euro",
"Verluste_Pro_Stunde",
"Gesamt_Verluste",
"Home_appliance_wh_per_hour",
]
for key in expected_keys:
assert key in result, f"The key '{key}' should be present in the result."
assert SimulationResult(**result) is not None
# Check the length of the main arrays
assert (
@@ -344,7 +326,7 @@ def test_simulation(create_ems_instance):
assert (
np.nansum(
np.where(
np.equal(result["Home_appliance_wh_per_hour"], None),
result["Home_appliance_wh_per_hour"] is None,
np.nan,
np.array(result["Home_appliance_wh_per_hour"]),
)

View File

@@ -44,13 +44,13 @@ def create_ems_instance(tmp_config: AppConfig) -> EnergieManagementSystem:
)
# Parameters based on previous example data
pv_prognose_wh = np.full(prediction_hours, 0)
pv_prognose_wh = [0.0] * prediction_hours
pv_prognose_wh[10] = 5000.0
pv_prognose_wh[11] = 5000.0
strompreis_euro_pro_wh = np.full(48, 0.001)
strompreis_euro_pro_wh[0:10] = 0.00001
strompreis_euro_pro_wh[11:15] = 0.00005
strompreis_euro_pro_wh = [0.001] * prediction_hours
strompreis_euro_pro_wh[0:10] = [0.00001] * 10
strompreis_euro_pro_wh[11:15] = [0.00005] * 4
strompreis_euro_pro_wh[20] = 0.00001
einspeiseverguetung_euro_pro_wh = [0.00007] * len(strompreis_euro_pro_wh)
@@ -116,9 +116,9 @@ def create_ems_instance(tmp_config: AppConfig) -> EnergieManagementSystem:
preis_euro_pro_wh_akku=0,
gesamtlast=gesamtlast,
),
wechselrichter=wechselrichter,
eauto=eauto,
home_appliance=home_appliance,
wechselrichter=wechselrichter,
)
ac = np.full(prediction_hours, 0)

View File

@@ -54,7 +54,7 @@ def test_optimize(
file = DIR_TESTDATA / fn_out
with file.open("r") as f_out:
expected_output_data = json.load(f_out)
expected_result = OptimizeResponse(**json.load(f_out))
opt_class = optimization_problem(tmp_config, fixed_seed=42)
start_hour = 10
@@ -72,9 +72,7 @@ def test_optimize(
# Assert that the output contains all expected entries.
# This does not assert that the optimization always gives the same result!
# Reproducibility and mathematical accuracy should be tested on the level of individual components.
compare_dict(ergebnis, expected_output_data)
compare_dict(ergebnis.model_dump(), expected_result.model_dump())
# The function creates a visualization result PDF as a side-effect.
visualisiere_ergebnisse_patch.assert_called_once()
OptimizeResponse(**ergebnis)

View File

@@ -49,7 +49,7 @@ def test_config_merge(tmp_path: Path) -> None:
with pytest.raises(ValueError):
# custom configuration is broken but not updated.
load_config(tmp_path, tmp_path, False)
load_config(tmp_path, True, False)
with config_file.open("r") as f_in:
# custom configuration is not changed.

View File

@@ -121,7 +121,7 @@ def test_update_ac_power_measurement(pv_forecast_instance, sample_forecast_start
forecast_start = pv_forecast_instance.get_forecast_start()
assert forecast_start == sample_forecast_start
updated = pv_forecast_instance.update_ac_power_measurement(forecast_start, 1000)
updated = pv_forecast_instance.update_ac_power_measurement(1000, forecast_start)
assert updated is True
forecast_data = pv_forecast_instance.get_forecast_data()
assert forecast_data[0].ac_power_measurement == 1000
@@ -130,7 +130,7 @@ def test_update_ac_power_measurement(pv_forecast_instance, sample_forecast_start
def test_update_ac_power_measurement_no_match(pv_forecast_instance):
"""Test updating AC power measurement where no date matches."""
date_time = datetime(2023, 10, 2, 1, 0, 0)
updated = pv_forecast_instance.update_ac_power_measurement(date_time, 1000)
updated = pv_forecast_instance.update_ac_power_measurement(1000, date_time)
assert not updated
@@ -265,7 +265,7 @@ def test_timezone_behaviour(
# Test updating AC power measurement for a specific date.
date_time = pv_forecast_instance.get_forecast_start()
assert date_time == sample_forecast_start
updated = pv_forecast_instance.update_ac_power_measurement(date_time, 1000)
updated = pv_forecast_instance.update_ac_power_measurement(1000, date_time)
assert updated is True
forecast_data = pv_forecast_instance.get_forecast_data()
assert forecast_data[0].ac_power_measurement == 1000