From f3139a2a18517a3b6df408641d5bd04f4aebe676 Mon Sep 17 00:00:00 2001 From: Joseph McKinsey Date: Wed, 21 Jan 2026 10:55:43 -0700 Subject: [PATCH 01/13] Update pyproject.toml with higher python version in ruff --- pyproject.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5e8ad6c..e4a41c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,8 +88,7 @@ select = [ #dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" #exclude = ["tests"] -# Assume Python 3.10. -target-version = "py38" +target-version = "py310" # 4. Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`. [tool.ruff.per-file-ignores] From eb6449bb429de456643257a9e854ab87cbc10ebe Mon Sep 17 00:00:00 2001 From: Joseph McKinsey Date: Wed, 21 Jan 2026 10:56:14 -0700 Subject: [PATCH 02/13] Run ruff check --fix --- .../componentframework/basic_component.py | 12 ++-- .../componentframework/mock_component.py | 5 +- .../system_configuration.py | 40 +++++------ .../wiring_diagram_utils.py | 5 +- src/oedisi/tools/broker_utils.py | 6 +- src/oedisi/tools/cli_tools.py | 36 +++++----- src/oedisi/tools/metrics.py | 5 +- src/oedisi/tools/testing_broker.py | 2 +- src/oedisi/types/common.py | 3 +- src/oedisi/types/data_types.py | 69 +++++++++---------- .../test_component_type.py | 3 +- .../test_oedisi_tools/broker/server.py | 4 +- .../component1/component1.py | 3 +- .../test_oedisi_tools/component1/server.py | 2 +- .../component2/component2.py | 3 +- .../test_oedisi_tools/component2/server.py | 4 +- .../component3/component3.py | 3 +- .../test_oedisi_tools/test_multi_container.py | 3 +- 18 files changed, 99 insertions(+), 109 deletions(-) diff --git a/src/oedisi/componentframework/basic_component.py b/src/oedisi/componentframework/basic_component.py index 27e9d43..6caece7 100644 --- a/src/oedisi/componentframework/basic_component.py +++ b/src/oedisi/componentframework/basic_component.py @@ -8,7 +8,7 @@ from . import system_configuration from .system_configuration import AnnotatedType from pydantic import BaseModel -from typing import List, Any, Dict +from typing import Any class ComponentDescription(BaseModel): @@ -30,12 +30,12 @@ class ComponentDescription(BaseModel): directory: str execute_function: str - static_inputs: List[AnnotatedType] - dynamic_inputs: List[AnnotatedType] - dynamic_outputs: List[AnnotatedType] + static_inputs: list[AnnotatedType] + dynamic_inputs: list[AnnotatedType] + dynamic_outputs: list[AnnotatedType] -def types_to_dict(types: List[AnnotatedType]): +def types_to_dict(types: list[AnnotatedType]): return {t.port_name: t for t in types} @@ -70,7 +70,7 @@ class BasicComponent(system_configuration.ComponentType): def __init__( self, name, - parameters: Dict[str, Any], + parameters: dict[str, Any], directory: str, host: str, port: int, diff --git a/src/oedisi/componentframework/mock_component.py b/src/oedisi/componentframework/mock_component.py index 5c9d34c..e947e81 100644 --- a/src/oedisi/componentframework/mock_component.py +++ b/src/oedisi/componentframework/mock_component.py @@ -11,7 +11,6 @@ import helics as h import logging -from typing import Dict import json import os from . import system_configuration @@ -27,7 +26,7 @@ class MockComponent(system_configuration.ComponentType): def __init__( self, name, - parameters: Dict[str, Dict[str, str]], + parameters: dict[str, dict[str, str]], directory: str, host: str = None, port: int = None, @@ -99,7 +98,7 @@ def __init__(self): self.fed = h.helicsCreateValueFederateFromConfig("helics_config.json") logger.info(f"Created federate {self.fed.name}") - with open("input_mapping.json", "r") as f: + with open("input_mapping.json") as f: port_mapping = json.load(f) self.subscriptions = {} for name, key in port_mapping.items(): diff --git a/src/oedisi/componentframework/system_configuration.py b/src/oedisi/componentframework/system_configuration.py index 5664723..f4d173d 100644 --- a/src/oedisi/componentframework/system_configuration.py +++ b/src/oedisi/componentframework/system_configuration.py @@ -14,7 +14,7 @@ """ from collections import defaultdict -from typing import List, Dict, Type, Any, Optional +from typing import Any import os import logging import shutil @@ -29,9 +29,9 @@ class AnnotatedType(BaseModel): """Represent the types of components and their interfaces.""" type: str - description: Optional[str] = None - unit: Optional[str] = None - port_id: Optional[str] = None + description: str | None = None + unit: str | None = None + port_id: str | None = None @property def port_name(self): @@ -68,7 +68,7 @@ class ComponentType(ABC): """ @abstractmethod - def generate_input_mapping(self, links: Dict[str, str]): + def generate_input_mapping(self, links: dict[str, str]): pass @abstractproperty @@ -109,10 +109,10 @@ class Component(BaseModel): name: str type: str - host: Optional[str] = None - container_port: Optional[int] = None + host: str | None = None + container_port: int | None = None image: str = "" - parameters: Dict[str, Any] + parameters: dict[str, Any] def port(self, port_name: str): return Port(name=self.name, port_name=port_name) @@ -128,15 +128,15 @@ def validate_image(cls, v, info: ValidationInfo): class ComponentStruct(BaseModel): component: Component - links: List[Link] + links: list[Link] class WiringDiagram(BaseModel): - "Cosimulation configuration. This may end up wrapped in another interface" + """Cosimulation configuration. This may end up wrapped in another interface""" name: str - components: List[Component] - links: List[Link] + components: list[Component] + links: list[Link] def clean_model(self, target_directory="."): for component in self.components: @@ -163,7 +163,7 @@ def clean_model(self, target_directory="."): @field_validator("components") @classmethod def check_component_names(cls, components): - "Check that the components all have unique names" + """Check that the components all have unique names""" names = set(map(lambda c: c.name, components)) assert len(names) == len(components) return components @@ -190,7 +190,7 @@ def empty(cls, name="unnamed"): class Federate(BaseModel): - "Federate configuration for HELICS CLI" + """Federate configuration for HELICS CLI""" directory: str hostname: str = "localhost" @@ -207,11 +207,11 @@ def get_federates_conn_info(wiring_diagram: WiringDiagram): def initialize_federates( wiring_diagram: WiringDiagram, - component_types: Dict[str, Type[ComponentType]], + component_types: dict[str, type[ComponentType]], compatability_checker, target_directory=".", -) -> List[Federate]: - "Initialize all the federates" +) -> list[Federate]: + """Initialize all the federates""" components = {} link_map = get_link_map(wiring_diagram) for component in wiring_diagram.components: @@ -279,17 +279,17 @@ class RunnerConfig(BaseModel): """ name: str - federates: List[Federate] + federates: list[Federate] def bad_compatability_checker(type1, type2): - "Basic compatability checker that says all types are compatible." + """Basic compatability checker that says all types are compatible.""" return True def generate_runner_config( wiring_diagram: WiringDiagram, - component_types: Dict[str, Type[ComponentType]], + component_types: dict[str, type[ComponentType]], compatibility_checker=bad_compatability_checker, target_directory=".", ): diff --git a/src/oedisi/componentframework/wiring_diagram_utils.py b/src/oedisi/componentframework/wiring_diagram_utils.py index 8054eda..e79c924 100644 --- a/src/oedisi/componentframework/wiring_diagram_utils.py +++ b/src/oedisi/componentframework/wiring_diagram_utils.py @@ -5,12 +5,11 @@ diagram composition and a programmatic interface. """ -from . import system_configuration from .system_configuration import WiringDiagram def get_graph(wiring_diagram: WiringDiagram): - "Get networkx graph representation of wiring_diagram" + """Get networkx graph representation of wiring_diagram""" import networkx as nx g = nx.MultiDiGraph() @@ -90,7 +89,7 @@ def plot_graph_bokeh(wiring_diagram: WiringDiagram): CustomJS, ) from bokeh.layouts import layout - from bokeh.io import output_file, show + from bokeh.io import show G = get_graph(wiring_diagram) graph_renderer = get_graph_renderer(G) diff --git a/src/oedisi/tools/broker_utils.py b/src/oedisi/tools/broker_utils.py index 2b51e4b..bdbe2c4 100644 --- a/src/oedisi/tools/broker_utils.py +++ b/src/oedisi/tools/broker_utils.py @@ -1,16 +1,16 @@ -import helics as h from pydantic import BaseModel class TimeData(BaseModel): - "Time data for a federate" + """Time data for a federate""" + name: str granted_time: float send_time: float def pprint_time_data(time_data): - "A table would be better somehow, but which should be the columns" + """A table would be better somehow, but which should be the columns""" print( f""" Name : {time_data.name} diff --git a/src/oedisi/tools/cli_tools.py b/src/oedisi/tools/cli_tools.py index 9a0c3db..fe5d653 100644 --- a/src/oedisi/tools/cli_tools.py +++ b/src/oedisi/tools/cli_tools.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any from pathlib import Path from uuid import uuid4 import subprocess @@ -42,7 +42,7 @@ def cli(): def bad_type_checker(type, x): - "Does not check types" + """Does not check types""" return True @@ -104,9 +104,9 @@ def build( oedisi build --component-dict components.json --system scenario.json \f + Parameters ---------- - target_directory : str (default="build") build path system : str (default="system.json") @@ -118,9 +118,8 @@ def build( broker_port: float The port of the broker. If using kubernetes, is internal to k8s """ - click.echo(f"Loading the components defined in {component_dict}") - with open(component_dict, "r") as f: + with open(component_dict) as f: component_dict_of_files = json.load(f) component_types = { @@ -222,7 +221,7 @@ def create_kubernetes_deployment( ) service_dict = drop_null_values(service.to_dict()) - with open(os.path.join(kube_folder, f"service.yml"), "w") as f: + with open(os.path.join(kube_folder, "service.yml"), "w") as f: yaml.dump(service_dict, f) broker_component = Component( @@ -278,17 +277,17 @@ def edit_docker_file(file_path, component: Component): with open(file_path, "w") as f: f.write(f"FROM {BASE_DOCKER_IMAGE}\n") - f.write(f"RUN apt-get update\n") - f.write(f"RUN apt-get install -y git ssh\n") + f.write("RUN apt-get update\n") + f.write("RUN apt-get install -y git ssh\n") #TODO: This works for now. Should be removed when a tagged release is available - f.write(f"RUN git clone https://github.com/openEDI/oedisi.git /oedisi\n") - f.write(f"RUN pip install /oedisi \n") + f.write("RUN git clone https://github.com/openEDI/oedisi.git /oedisi\n") + f.write("RUN pip install /oedisi \n") f.write(f"RUN mkdir {component.type}\n") f.write(f"COPY . ./{component.type}\n") f.write(f"WORKDIR ./{component.type}\n") - f.write(f"RUN pip install -r requirements.txt\n") + f.write("RUN pip install -r requirements.txt\n") f.write(f"EXPOSE {component.container_port}/tcp\n") cmd = f"CMD {['python', 'server.py']}\n" cmd = cmd.replace("'", '"') @@ -296,7 +295,7 @@ def edit_docker_file(file_path, component: Component): pass -def edit_docker_files(wiring_diagram: WiringDiagram, component_types: Dict): +def edit_docker_files(wiring_diagram: WiringDiagram, component_types: dict): parsed_components = [] for component in wiring_diagram.components: if component.type not in parsed_components: @@ -307,7 +306,7 @@ def edit_docker_files(wiring_diagram: WiringDiagram, component_types: Dict): def create_docker_compose_file( - wiring_diagram: WiringDiagram, target_directory: str, broker_port: int, component_types: Dict, simulation_id: str + wiring_diagram: WiringDiagram, target_directory: str, broker_port: int, component_types: dict, simulation_id: str ): config = {"services": {}, "networks": {}} @@ -478,9 +477,9 @@ def test_description(target_directory, component_desc, parameters): \f + Parameters ---------- - target_directory : str build location @@ -499,7 +498,6 @@ def test_description(target_directory, component_desc, parameters): inputs and outputs (basically recorder federate?) Create and run system with wiring diagram """ - with open(component_desc) as f: comp_desc = ComponentDescription.model_validate(json.load(f)) comp_desc.directory = os.path.dirname(component_desc) @@ -568,7 +566,7 @@ def test_description(target_directory, component_desc, parameters): def remove_from_runner_config(runner_config, element): - "Remove federate from configuration" + """Remove federate from configuration""" within_feds = [fed for fed in runner_config.federates if fed.name != element] without_feds = [fed for fed in runner_config.federates if fed.name == element] new_config = RunnerConfig(name=runner_config.name, federates=within_feds) @@ -576,8 +574,8 @@ def remove_from_runner_config(runner_config, element): def remove_from_json(system_json, element): - "Remove federate from configuration and resave with revised.json" - with open(system_json, "r") as f: + """Remove federate from configuration and resave with revised.json""" + with open(system_json) as f: runner_config = RunnerConfig.model_validate(json.load(f)) new_config, without_feds = remove_from_runner_config(runner_config, element) @@ -605,9 +603,9 @@ def debug_component(runner, foreground): and then run our debugging component in standard in / standard out. \f + Parameters ---------- - runner : str filepath to system runner json diff --git a/src/oedisi/tools/metrics.py b/src/oedisi/tools/metrics.py index 6167143..ea7eca5 100644 --- a/src/oedisi/tools/metrics.py +++ b/src/oedisi/tools/metrics.py @@ -5,7 +5,8 @@ - Mean squared relative error. For angles: -- Mean absolute angle error.""" +- Mean absolute angle error. +""" import click from pathlib import Path @@ -60,9 +61,9 @@ def evaluate_estimate(path, metric, angle_unit): - MAAE: Mean absolute angle error. \f + Parameters ---------- - path : Path Path to the folder containing the measurement files. diff --git a/src/oedisi/tools/testing_broker.py b/src/oedisi/tools/testing_broker.py index 92527c4..7e73187 100644 --- a/src/oedisi/tools/testing_broker.py +++ b/src/oedisi/tools/testing_broker.py @@ -55,6 +55,6 @@ def wait_until_connected(self): current_state = self.broker.query("broker", "current_state") cores = current_state["cores"] if len(cores) == 2 and all( - (core["state"] == "init_requested" for core in cores) + core["state"] == "init_requested" for core in cores ): return diff --git a/src/oedisi/types/common.py b/src/oedisi/types/common.py index 15ca5d2..12a6b3d 100644 --- a/src/oedisi/types/common.py +++ b/src/oedisi/types/common.py @@ -1,4 +1,3 @@ -from typing import Dict, Optional from pydantic import BaseModel from enum import Enum @@ -29,4 +28,4 @@ class HeathCheck(BaseModel): class ServerReply(BaseModel): detail: str - action: Optional[str] = None + action: str | None = None diff --git a/src/oedisi/types/data_types.py b/src/oedisi/types/data_types.py index 2deb1b7..c5cea33 100644 --- a/src/oedisi/types/data_types.py +++ b/src/oedisi/types/data_types.py @@ -2,13 +2,12 @@ import datetime from enum import Enum from pydantic import model_validator, BaseModel, RootModel -from typing import List, Optional, Union, Tuple ### Supporting Functions ### # TODO: Connect with CIM values -Complex = Tuple[float, float] +Complex = tuple[float, float] class StateArray(BaseModel): @@ -20,9 +19,9 @@ class StateArray(BaseModel): """ - values: List[int] - ids: List[str] - time: Optional[datetime.datetime] = None + values: list[int] + ids: list[str] + time: datetime.datetime | None = None class SwitchStates(StateArray): @@ -48,10 +47,10 @@ class CostArray(BaseModel): """ - values: List[List[float]] - ids: List[str] + values: list[list[float]] + ids: list[str] units: str = "$" - time: Optional[datetime.datetime] = None + time: datetime.datetime | None = None class RealCostFunctions(CostArray): @@ -82,12 +81,12 @@ class MeasurementArray(BaseModel): "EquipmentNodeArray" """ - values: List[float] - ids: List[str] + values: list[float] + ids: list[str] units: str - accuracy: Optional[List[float]] = None - bad_data_threshold: Optional[List[float]] = None - time: Optional[datetime.datetime] = None + accuracy: list[float] | None = None + bad_data_threshold: list[float] | None = None + time: datetime.datetime | None = None class BusArray(MeasurementArray): @@ -137,7 +136,7 @@ class EquipmentNodeArray(MeasurementArray): """ - equipment_ids: List[str] + equipment_ids: list[str] class VoltagesMagnitude(BusArray): @@ -221,32 +220,32 @@ class StatesOfCharge(EquipmentArray): class Topology(BaseModel): - admittance: Union[AdmittanceSparse, AdmittanceMatrix] + admittance: AdmittanceSparse | AdmittanceMatrix injections: Injection - incidences: Optional[IncidenceList] = None - base_voltage_angles: Optional[VoltagesAngle] = None - base_voltage_magnitudes: Optional[VoltagesMagnitude] = None - slack_bus: List[str] = [] + incidences: IncidenceList | None = None + base_voltage_angles: VoltagesAngle | None = None + base_voltage_magnitudes: VoltagesMagnitude | None = None + slack_bus: list[str] = [] class Incidence(BaseModel): - from_equipment: List[str] - to_equipment: List[str] - equipment_type: Optional[List[str]] = None + from_equipment: list[str] + to_equipment: list[str] + equipment_type: list[str] | None = None class IncidenceList(Incidence): - ids: List[str] + ids: list[str] class AdmittanceSparse(Incidence): - admittance_list: List[Complex] + admittance_list: list[Complex] units: str = "S" class AdmittanceMatrix(BaseModel): - admittance_matrix: List[List[Complex]] - ids: List[str] + admittance_matrix: list[list[Complex]] + ids: list[str] units: str = "S" @@ -273,7 +272,7 @@ class Command(BaseModel): val: str -CommandList = RootModel[List[Command]] +CommandList = RootModel[list[Command]] class ReactivePowerSetting(Enum): @@ -299,24 +298,24 @@ class VVControl(BaseModel): varchangetolerance: float = 0.025 voltagechangetolerance: float = 0.0001 vv_refreactivepower: ReactivePowerSetting = ReactivePowerSetting.VARAVAL_WATTS - voltage: List[float] # p.u. in V - reactive_response: List[float] # p.u. in VArs + voltage: list[float] # p.u. in V + reactive_response: list[float] # p.u. in VArs class VWControl(BaseModel): """OpenDSS setting for volt-watt control.""" deltap_factor: float = -1.0 # -1.0 tells OpenDSS to figure it out - voltage: List[float] # p.u. in V - power_response: List[float] # p.u. in VArs + voltage: list[float] # p.u. in V + power_response: list[float] # p.u. in VArs class InverterControl(BaseModel): """InverterControl with volt-var control and/or volt-watt control.""" - pvsystem_list: Optional[List[str]] = None - vvcontrol: Optional[VVControl] = None - vwcontrol: Optional[VWControl] = None + pvsystem_list: list[str] | None = None + vvcontrol: VVControl | None = None + vwcontrol: VWControl | None = None mode: InverterControlMode = InverterControlMode.voltvar @model_validator(mode="before") @@ -336,4 +335,4 @@ def check_mode(cls, values): return values -InverterControlList = RootModel[List[InverterControl]] +InverterControlList = RootModel[list[InverterControl]] diff --git a/tests/test_basic_system/test_component_type/test_component_type.py b/tests/test_basic_system/test_component_type/test_component_type.py index 9fbd61c..c89f4a5 100644 --- a/tests/test_basic_system/test_component_type/test_component_type.py +++ b/tests/test_basic_system/test_component_type/test_component_type.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ """ @@ -34,7 +33,7 @@ def __init__(self): self.fed = h.helicsCreateValueFederate(self.parameters["name"], fedinfo) logger.info(f"Created federate {self.fed.name}") - with open("input_mapping.json", "r") as f: + with open("input_mapping.json") as f: port_mapping = json.load(f) self.subscriptions = {} if "test1" in port_mapping: diff --git a/tests/unit_tests/test_oedisi_tools/broker/server.py b/tests/unit_tests/test_oedisi_tools/broker/server.py index eec8e61..39cca83 100644 --- a/tests/unit_tests/test_oedisi_tools/broker/server.py +++ b/tests/unit_tests/test_oedisi_tools/broker/server.py @@ -1,4 +1,4 @@ -from fastapi import FastAPI, BackgroundTasks, UploadFile +from fastapi import FastAPI, BackgroundTasks from fastapi.exceptions import HTTPException from fastapi.responses import JSONResponse @@ -161,7 +161,7 @@ async def run_feeder(background_tasks: BackgroundTasks): logger.info("Run Called on Broker service") try: background_tasks.add_task(run_simulation) - except Exception as e: + except Exception: err = traceback.format_exc() raise HTTPException(status_code=404, detail=str(err)) diff --git a/tests/unit_tests/test_oedisi_tools/component1/component1.py b/tests/unit_tests/test_oedisi_tools/component1/component1.py index 3ee34ed..32d2528 100644 --- a/tests/unit_tests/test_oedisi_tools/component1/component1.py +++ b/tests/unit_tests/test_oedisi_tools/component1/component1.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ """ from oedisi.types.common import BrokerConfig @@ -37,7 +36,7 @@ def __init__(self, broker_config: BrokerConfig = BrokerConfig()): self.fed = h.helicsCreateValueFederate(self.parameters["name"], fedinfo) logger.info(f"Created federate {self.fed.name}") - with open("input_mapping.json", "r") as f: + with open("input_mapping.json") as f: port_mapping = json.load(f) self.subscriptions = {} if "test1" in port_mapping: diff --git a/tests/unit_tests/test_oedisi_tools/component1/server.py b/tests/unit_tests/test_oedisi_tools/component1/server.py index a80f210..81bd9c8 100644 --- a/tests/unit_tests/test_oedisi_tools/component1/server.py +++ b/tests/unit_tests/test_oedisi_tools/component1/server.py @@ -58,7 +58,7 @@ async def configure(component_struct:ComponentStruct): with open(DefaultFileNames.STATIC_INPUTS.value, "w") as f: json.dump(params, f) response = ServerReply( - detail = f"Sucessfully updated configuration files." + detail = "Sucessfully updated configuration files." ).model_dump() return JSONResponse(response, 200) diff --git a/tests/unit_tests/test_oedisi_tools/component2/component2.py b/tests/unit_tests/test_oedisi_tools/component2/component2.py index a59ef2e..68b9642 100644 --- a/tests/unit_tests/test_oedisi_tools/component2/component2.py +++ b/tests/unit_tests/test_oedisi_tools/component2/component2.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ """ @@ -44,7 +43,7 @@ def __init__(self, broker_config: BrokerConfig = BrokerConfig()): self.fed = h.helicsCreateValueFederate(self.parameters["name"], fedinfo) logger.info(f"Created federate {self.fed.name}") - with open("input_mapping.json", "r") as f: + with open("input_mapping.json") as f: port_mapping = json.load(f) self.subscriptions = {} if "test1" in port_mapping: diff --git a/tests/unit_tests/test_oedisi_tools/component2/server.py b/tests/unit_tests/test_oedisi_tools/component2/server.py index e1094f7..92ec499 100644 --- a/tests/unit_tests/test_oedisi_tools/component2/server.py +++ b/tests/unit_tests/test_oedisi_tools/component2/server.py @@ -47,7 +47,7 @@ async def run_model(broker_config: BrokerConfig, background_tasks: BackgroundTas background_tasks.add_task(federate.run) logger.info("Federate 2 started") return {"reply": "success", "error": False} - except Exception as e: + except Exception: err = traceback.format_exc() raise HTTPException(500,str(err)) @@ -65,7 +65,7 @@ async def configure(component_struct:ComponentStruct): with open(DefaultFileNames.STATIC_INPUTS.value, "w") as f: json.dump(params, f) response = ServerReply( - detail = f"Sucessfully updated configuration files." + detail = "Sucessfully updated configuration files." ).model_dump() return JSONResponse(response, 200) diff --git a/tests/unit_tests/test_oedisi_tools/component3/component3.py b/tests/unit_tests/test_oedisi_tools/component3/component3.py index b65ca54..c4b70c3 100644 --- a/tests/unit_tests/test_oedisi_tools/component3/component3.py +++ b/tests/unit_tests/test_oedisi_tools/component3/component3.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ """ @@ -37,7 +36,7 @@ def __init__(self): logger.info(f"Created federate {self.fed.name}") time.sleep(3) - with open("input_mapping.json", "r") as f: + with open("input_mapping.json") as f: port_mapping = json.load(f) self.subscriptions = {} if "test1" in port_mapping: diff --git a/tests/unit_tests/test_oedisi_tools/test_multi_container.py b/tests/unit_tests/test_oedisi_tools/test_multi_container.py index 2469ed8..b46acb1 100644 --- a/tests/unit_tests/test_oedisi_tools/test_multi_container.py +++ b/tests/unit_tests/test_oedisi_tools/test_multi_container.py @@ -15,7 +15,6 @@ from oedisi.types.common import BROKER_SERVICE, HeathCheck from oedisi.componentframework.system_configuration import ( - ComponentStruct, WiringDiagram, ) from requests.exceptions import ConnectionError @@ -87,7 +86,7 @@ def test_api_run(base_path: Path, monkeypatch: pytest.MonkeyPatch): assert build_path.exists(), "Build path for the test project does not exist." assert (build_path / "docker-compose.yml").exists(), "Build path for the test project does not exist." - payload = json.load(open(base_path / "system.json", 'r')) + payload = json.load(open(base_path / "system.json")) wiring_diagram = WiringDiagram(**payload) clients = {} From e5fe566a875cbaac8f818ac702a9fcb93d2c4189 Mon Sep 17 00:00:00 2001 From: Joseph McKinsey Date: Wed, 21 Jan 2026 10:58:15 -0700 Subject: [PATCH 03/13] Add precommit json --- .pre-commit-config.yaml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .pre-commit-config.yaml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..433da05 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,33 @@ +repos: + # Ruff for linting and formatting + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.6 + hooks: + # Run the linter + - id: ruff + args: [--fix] + # Run the formatter + - id: ruff-format + + # Pre-commit hooks for common issues + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-json + - id: check-added-large-files + - id: check-merge-conflict + - id: check-toml + - id: mixed-line-ending + + # Type checking with mypy + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.14.1 + hooks: + - id: mypy + additional_dependencies: + - pydantic~=2.0 + - types-PyYAML + args: [--ignore-missing-imports] From 2bccf540b74cd68ba705677e7aa8b7e29eabb964 Mon Sep 17 00:00:00 2001 From: Joseph McKinsey Date: Wed, 21 Jan 2026 11:00:03 -0700 Subject: [PATCH 04/13] Run precommit on all files --- .gitignore | 1 - .vscode/settings.json | 2 +- README.md | 2 +- docs/conf.py | 8 +- docs/images/step4.excalidraw | 2 +- docs/images/step_1.svg | 6 +- docs/images/step_2a.svg | 6 +- docs/images/step_2b.svg | 6 +- docs/images/step_3a.svg | 6 +- docs/images/step_3b.svg | 6 +- docs/images/step_4.svg | 6 +- docs/multi_container.rst | 20 ++-- docs/oedisi_cli.rst | 34 +++--- .../componentframework/mock_component.py | 8 +- .../componentframework/mock_component.sh | 1 - .../system_configuration.py | 12 +- .../wiring_diagram_utils.py | 8 +- src/oedisi/tools/cli_tools.py | 111 ++++++++++-------- src/oedisi/tools/metrics.py | 26 ++-- src/oedisi/tools/testing_broker.py | 4 +- src/oedisi/types/common.py | 2 +- .../types/schemas/AdmittanceMatrix.json | 2 +- .../types/schemas/AdmittanceSparse.json | 2 +- src/oedisi/types/schemas/CapacitorStates.json | 2 +- src/oedisi/types/schemas/Command.json | 2 +- src/oedisi/types/schemas/CommandList.json | 2 +- src/oedisi/types/schemas/CostArray.json | 2 +- src/oedisi/types/schemas/CurrentsAngle.json | 2 +- .../types/schemas/CurrentsImaginary.json | 2 +- .../types/schemas/CurrentsMagnitude.json | 2 +- src/oedisi/types/schemas/CurrentsReal.json | 2 +- src/oedisi/types/schemas/Injection.json | 2 +- src/oedisi/types/schemas/InverterControl.json | 2 +- .../types/schemas/InverterControlList.json | 2 +- .../types/schemas/MeasurementArray.json | 2 +- .../types/schemas/OperationalCosts.json | 2 +- src/oedisi/types/schemas/PowersAngle.json | 2 +- src/oedisi/types/schemas/PowersImaginary.json | 2 +- src/oedisi/types/schemas/PowersMagnitude.json | 2 +- src/oedisi/types/schemas/PowersReal.json | 2 +- .../types/schemas/ReactiveCostFunctions.json | 2 +- .../schemas/ReactiveWholesalePrices.json | 2 +- .../types/schemas/RealCostFunctions.json | 2 +- .../types/schemas/RealWholesalePrices.json | 2 +- src/oedisi/types/schemas/RegulatorStates.json | 2 +- .../schemas/RootModel[List[Command]].json | 2 +- .../RootModel[List[InverterControl]].json | 2 +- .../types/schemas/SolarIrradiances.json | 2 +- src/oedisi/types/schemas/StateArray.json | 2 +- src/oedisi/types/schemas/StatesOfCharge.json | 2 +- src/oedisi/types/schemas/SwitchStates.json | 2 +- src/oedisi/types/schemas/Temperatures.json | 2 +- src/oedisi/types/schemas/Topology.json | 2 +- src/oedisi/types/schemas/VVControl.json | 2 +- src/oedisi/types/schemas/VWControl.json | 2 +- src/oedisi/types/schemas/VoltagesAngle.json | 2 +- .../types/schemas/VoltagesImaginary.json | 2 +- .../types/schemas/VoltagesMagnitude.json | 2 +- src/oedisi/types/schemas/VoltagesReal.json | 2 +- src/oedisi/types/schemas/WindSpeeds.json | 2 +- .../test_component_type.py | 7 +- .../test_data_types/data/Topology.1.json | 2 +- .../test_data_types/data/Topology.2.json | 2 +- .../test_oedisi_tools/broker/Dockerfile | 4 +- .../test_oedisi_tools/broker/requirements.txt | 1 - .../test_oedisi_tools/broker/server.py | 27 +++-- .../component1/component1.py | 12 +- .../component1/component_definition.json | 2 +- .../component1/requirements.txt | 2 +- .../test_oedisi_tools/component1/server.py | 10 +- .../component2/component2.py | 13 +- .../component2/component_definition.json | 2 +- .../component2/requirements.txt | 1 - .../test_oedisi_tools/component2/server.py | 11 +- .../component3/component3.py | 7 +- .../test_oedisi_tools/test_multi_container.py | 61 +++++----- 76 files changed, 251 insertions(+), 268 deletions(-) diff --git a/.gitignore b/.gitignore index 81a6083..7b76b58 100644 --- a/.gitignore +++ b/.gitignore @@ -161,4 +161,3 @@ cython_debug/ *.bak *.DS_Store - diff --git a/.vscode/settings.json b/.vscode/settings.json index 0f42b6e..c86a56b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { "python.pythonPath": "C:\\Users\\alatif\\.conda\\envs\\gadal\\python.exe", "esbonio.sphinx.confDir": "" -} \ No newline at end of file +} diff --git a/README.md b/README.md index 8f5ef71..d2ed5f7 100644 --- a/README.md +++ b/README.md @@ -24,4 +24,4 @@ The repository [`sgidal-example`](https://github.com/openEDI/sgidal-example/) co - OpenDSS federate - measuring federate - weighted least squares state estimator federate -- recording federates \ No newline at end of file +- recording federates diff --git a/docs/conf.py b/docs/conf.py index 614eefb..66aa918 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -29,7 +29,13 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ["sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx_click", 'sphinx.ext.intersphinx', 'sphinxcontrib.youtube'] +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx_click", + "sphinx.ext.intersphinx", + "sphinxcontrib.youtube", +] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] diff --git a/docs/images/step4.excalidraw b/docs/images/step4.excalidraw index fec2e19..9434fa6 100644 --- a/docs/images/step4.excalidraw +++ b/docs/images/step4.excalidraw @@ -4693,4 +4693,4 @@ "viewBackgroundColor": "#ffffff" }, "files": {} -} \ No newline at end of file +} diff --git a/docs/images/step_1.svg b/docs/images/step_1.svg index 6d6b6bd..5996a35 100644 --- a/docs/images/step_1.svg +++ b/docs/images/step_1.svg @@ -1,6 +1,6 @@ - + - + - oedisi build -m -p 8766>Use this flag to build files for docker compose and kubernetesUse this option toset up broker port(default 8766)This CLI command builds files for both DOCKER COMPOSE and KUBERNETESin the 'build' folder.Each participating federate is assigned a port number using 'environment variabe \ No newline at end of file + oedisi build -m -p 8766>Use this flag to build files for docker compose and kubernetesUse this option toset up broker port(default 8766)This CLI command builds files for both DOCKER COMPOSE and KUBERNETESin the 'build' folder.Each participating federate is assigned a port number using 'environment variabe diff --git a/docs/images/step_2a.svg b/docs/images/step_2a.svg index 3f43b78..9cdd0bf 100644 --- a/docs/images/step_2a.svg +++ b/docs/images/step_2a.svg @@ -1,6 +1,6 @@ - + - + - services:oedisi_broker:build:context: ../broker/.environment:PORT: '8766'hostname: brokerimage: aadillatif/oedisi_brokernetworks:custom-network: {}ports:- 8766:8766Services lists all federates participating in the co-simulationif __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=int(os.environ['PORT']))Multiple services might end up using the same imageHostname should always be uniquePort is forwarded to enable co-simulation via REST-API endpointsService port is set up using the environment varible portAPI server should start on this port \ No newline at end of file + services:oedisi_broker:build:context: ../broker/.environment:PORT: '8766'hostname: brokerimage: aadillatif/oedisi_brokernetworks:custom-network: {}ports:- 8766:8766Services lists all federates participating in the co-simulationif __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=int(os.environ['PORT']))Multiple services might end up using the same imageHostname should always be uniquePort is forwarded to enable co-simulation via REST-API endpointsService port is set up using the environment varible portAPI server should start on this port diff --git a/docs/images/step_2b.svg b/docs/images/step_2b.svg index 8f18fb7..3217543 100644 --- a/docs/images/step_2b.svg +++ b/docs/images/step_2b.svg @@ -1,6 +1,6 @@ - + - + - cd >docker compose up>Starts all the containers<build folder> \ No newline at end of file + cd >docker compose up>Starts all the containers<build folder> diff --git a/docs/images/step_3a.svg b/docs/images/step_3a.svg index 6b073d4..a28857c 100644 --- a/docs/images/step_3a.svg +++ b/docs/images/step_3a.svg @@ -1,6 +1,6 @@ - + - + - A 'deployment' file is generated for each federate. A single 'service.yml' file is generated to set up kubernetes serviceapiVersion: v1kind: Podmetadata:labels:app: oedisiname: broker-podspec:containers:- env:- name: PORTvalue: '8766'- name: SERVICE_NAMEvalue: oedisi-serviceimage: aadillatif/oedisi_broker:latestname: brokerports:- containerPort: 8766hostname: brokersubdomain: oedisi-serviceService port is set up using the environment varible port(Consistant with docker compose implementation)Each federate runs in a separate pod. Pod name should be uniqueConsistant across all deployment filesThis environment variable serves two purposes1. Used to identify if the co-simulation is running in is_kubernetes_env = os.environ['SERVICE_NAME'] if 'SERVICE_NAME' in os.environ else None2. Used to automaticlly build URLs to communicate with other federatesif is_kubernetes_env: SERVICE_NAME = os.environ['SERVICE_NAME'] url = f"http://{host}.{SERVICE_NAME}:{port}/" else: url = f"http://{host}:{port}/"Used for docker compose deploymentUsed for kubernetes deployment \ No newline at end of file + A 'deployment' file is generated for each federate. A single 'service.yml' file is generated to set up kubernetes serviceapiVersion: v1kind: Podmetadata:labels:app: oedisiname: broker-podspec:containers:- env:- name: PORTvalue: '8766'- name: SERVICE_NAMEvalue: oedisi-serviceimage: aadillatif/oedisi_broker:latestname: brokerports:- containerPort: 8766hostname: brokersubdomain: oedisi-serviceService port is set up using the environment varible port(Consistant with docker compose implementation)Each federate runs in a separate pod. Pod name should be uniqueConsistant across all deployment filesThis environment variable serves two purposes1. Used to identify if the co-simulation is running in is_kubernetes_env = os.environ['SERVICE_NAME'] if 'SERVICE_NAME' in os.environ else None2. Used to automaticlly build URLs to communicate with other federatesif is_kubernetes_env: SERVICE_NAME = os.environ['SERVICE_NAME'] url = f"http://{host}.{SERVICE_NAME}:{port}/" else: url = f"http://{host}:{port}/"Used for docker compose deploymentUsed for kubernetes deployment diff --git a/docs/images/step_3b.svg b/docs/images/step_3b.svg index 7c01aaa..2e48246 100644 --- a/docs/images/step_3b.svg +++ b/docs/images/step_3b.svg @@ -1,6 +1,6 @@ - + - + - build_path = kubectl apply -f {build_path}>Applies all deployment and service files in the given folder.Starts all the pods. (each running a single federate)<build folder> / kubernetes \ No newline at end of file + build_path = kubectl apply -f {build_path}>Applies all deployment and service files in the given folder.Starts all the pods. (each running a single federate)<build folder> / kubernetes diff --git a/docs/images/step_4.svg b/docs/images/step_4.svg index 83df3e7..fb55564 100644 --- a/docs/images/step_4.svg +++ b/docs/images/step_4.svg @@ -1,6 +1,6 @@ - + - + - 4. (Optional) A running co-simualtion can be terminated by making a GEt request to 1. Make a POST request to pass the as payload {broker api}/configureJSONWiringDiagramThis sets up INPUT_MAPPING and STATIC_INPUTS files on each federate2 (Optional) Make POST requests to i. ii.{broker api}/model{broker api}/profilesZIP file containing load and PV profilesI. load profiles should be in a 'load_profiles' subfolderII. pv profiles should be in a 'pv_profiles' subfolder ZIP file containing the OpenDSS model Should have a "master.dss" file3. Make a POST requests to{broker api}/runStarts co-simulation on all federates5. Make a GET request to {broker api}/terminateDownload all recorders in a single zip file{broker api}/results \ No newline at end of file + 4. (Optional) A running co-simualtion can be terminated by making a GEt request to 1. Make a POST request to pass the as payload {broker api}/configureJSONWiringDiagramThis sets up INPUT_MAPPING and STATIC_INPUTS files on each federate2 (Optional) Make POST requests to i. ii.{broker api}/model{broker api}/profilesZIP file containing load and PV profilesI. load profiles should be in a 'load_profiles' subfolderII. pv profiles should be in a 'pv_profiles' subfolder ZIP file containing the OpenDSS model Should have a "master.dss" file3. Make a POST requests to{broker api}/runStarts co-simulation on all federates5. Make a GET request to {broker api}/terminateDownload all recorders in a single zip file{broker api}/results diff --git a/docs/multi_container.rst b/docs/multi_container.rst index 5e9fcd0..025e769 100644 --- a/docs/multi_container.rst +++ b/docs/multi_container.rst @@ -1,7 +1,7 @@ Multi-Container Setup ********************* -In this user guide, we provide step by step instructions for orchestration of multi-container co-simulation for an existing OEDISI project. +In this user guide, we provide step by step instructions for orchestration of multi-container co-simulation for an existing OEDISI project. Multi-container co-simulations requires containers to communicate with other containers participating in the co-simulation federation. This is achieved via a REST API interface. This interface allows users to start / stop co-simulation runs, upload profiles and models and download co-simulation results once the simulation is complete. @@ -15,15 +15,15 @@ Step 0 - Requirements for a multi-container setup User has two main responsibilities when integrating a new container (see https://github.com/openEDI/oedisi-example/tree/main/wls_federate). -1. Implementation of a REST API interface for the algorithm. Three required endpoints are: - +1. Implementation of a REST API interface for the algorithm. Three required endpoints are: + - GET ("/") : This root endpoint is used for container health Check - POST ("/configure"): This endpoint takes Component Structure as JSON payload and is used to configure the federate - POST ("/run") : This endpoint takes broker config JSON as payload and starts the federate (connects to the broker and takes federate to HELICS execution mode) A minimal example is provided below -.. code-block:: python +.. code-block:: python from fastapi import FastAPI, BackgroundTasks, HTTPException from state_estimator_federate import run_simulator @@ -68,7 +68,7 @@ A minimal example is provided below @app.post("/configure") - async def configure(component_struct:ComponentStruct): + async def configure(component_struct:ComponentStruct): component = component_struct.component params = component.parameters params["name"] = component.name @@ -79,7 +79,7 @@ A minimal example is provided below json.dump(params , open(DefaultFileNames.STATIC_INPUTS.value, "w")) response = ServerReply( detail = f"Sucessfully updated configuration files." - ).dict() + ).dict() return JSONResponse(response, 200) if __name__ == "__main__": @@ -116,8 +116,8 @@ Deployment files for Docker-Compose and Kubernetes are built using the following Step 2a - Running containers using Docker-Compose ------------------------------------------------- -OEDISI supports multi-container orchestration via Docker-Compose and Kubernetes. -Docker-Compose is well suited for single machine multi-container co-simulation run. +OEDISI supports multi-container orchestration via Docker-Compose and Kubernetes. +Docker-Compose is well suited for single machine multi-container co-simulation run. The image below provides details on the auto-generate Docker-Compose file. @@ -147,7 +147,7 @@ Containers can be orchestrated using Kubernetes using the following CLI command Step 3 - Running containers using Kubernetes -------------------------------------------- -In this multi-container implementation, the 'Broker' container is the sole container the user is expected to interface with. +In this multi-container implementation, the 'Broker' container is the sole container the user is expected to interface with. The API endpoints (listed above) enables the broker federate to users to setup and run co-simulations .. image:: images/step_4.png @@ -165,4 +165,4 @@ FAQs ---- - If build is not using local changes, then try docker compose build --no-cache. -- If using M1 or M2, HELICS in Docker requires x86 emulation with by using export DOCKER_DEFAULT_PLATFORM=linux/amd64 \ No newline at end of file +- If using M1 or M2, HELICS in Docker requires x86 emulation with by using export DOCKER_DEFAULT_PLATFORM=linux/amd64 diff --git a/docs/oedisi_cli.rst b/docs/oedisi_cli.rst index 98c7774..9ce2957 100644 --- a/docs/oedisi_cli.rst +++ b/docs/oedisi_cli.rst @@ -89,11 +89,11 @@ Multi-Container REST API endpoint requirements Multi-Container Model Setup --------------------------- -The ``oedisi`` frameworks enables users to set up models as single-container (all models running -in a single Docker container) or multi-container implementation (all componenets running in -seperate docker containers.). The framework currently supports both Docker-compose and kubernetes -configurations. By default, the ``oedisi`` frameworks sets up the single-container simulation. -``-m`` flag can be used to build additional files needed for either Docker-compose or Kubernetes +The ``oedisi`` frameworks enables users to set up models as single-container (all models running +in a single Docker container) or multi-container implementation (all componenets running in +seperate docker containers.). The framework currently supports both Docker-compose and kubernetes +configurations. By default, the ``oedisi`` frameworks sets up the single-container simulation. +``-m`` flag can be used to build additional files needed for either Docker-compose or Kubernetes orchestration. Uploading user defined models requires setting the 'user_uploads_model' to true. If the flag is set to false, the model will be downloaded automatically from AWS. @@ -157,10 +157,10 @@ Create multi-container artifacts in a custom folder using a specific system file oedisi build -m --target-directory my_build --system my_system.json -Once the build process is complete, the containers can be launched by either Docker-compose -(all images will run on the local machine) or using Kubernetes (enables orchestration across multiple -machines). Navigate to the build folder and execute following command to lauch all images in the -build folder. +Once the build process is complete, the containers can be launched by either Docker-compose +(all images will run on the local machine) or using Kubernetes (enables orchestration across multiple +machines). Navigate to the build folder and execute following command to lauch all images in the +build folder. Running containers using Docker-compose:: @@ -187,32 +187,32 @@ to port 8080 on the local machine. The REST API endpoint can then be accessed at ``http://localhost:8080`` -By default, the deployment file is configured to download required images from Docker Hub. -Users have to option to modify the deployment file and use a local registery (https://docs.docker.com/registry/) +By default, the deployment file is configured to download required images from Docker Hub. +Users have to option to modify the deployment file and use a local registery (https://docs.docker.com/registry/) to use local images instead. Multi-Container Model Run --------------------------- Once all required docker images are running (see last section), simulation run can be orchestration using the REST API interface. -The interface aloows users to +The interface aloows users to #. Upload private data (distribution models and associated profiles) #. Launch the simulation #. Retrieve simulation results IPs for containers and corresponding exposed ports are available to users within the ``docker-compose.yml`` file in the main build folder. -To check healh of the API server the runnig container, user can open a web browser and browse to ``http://{ip}:{port}``, where ``ip`` +To check healh of the API server the runnig container, user can open a web browser and browse to ``http://{ip}:{port}``, where ``ip`` and ``port`` are container specific (see ``docker-compose.yml``). -Identify the ``ip`` and ``port`` information for ``oedisi_broker`` container from the ``docker-compose.yml`` file. +Identify the ``ip`` and ``port`` information for ``oedisi_broker`` container from the ``docker-compose.yml`` file. Upload private data ++++++++++++++++++++ -#. Distribution models, compressed in ``zip`` format can be uploaded by making a POST request to the following endpoint ``http://{ip}:{port}/model`` -#. Similarly, load profile, compressed in ``zip`` format can be uploaded by making a POST request to the following endpoint ``http://{ip}:{port}/profiles`` +#. Distribution models, compressed in ``zip`` format can be uploaded by making a POST request to the following endpoint ``http://{ip}:{port}/model`` +#. Similarly, load profile, compressed in ``zip`` format can be uploaded by making a POST request to the following endpoint ``http://{ip}:{port}/profiles`` These files are automatically unzipped server side after a sucessful upload. @@ -227,7 +227,7 @@ Retrieve simulation results +++++++++++++++++++++++++++ #. Identify the ``ips`` and ``ports`` information for ``oedisi_broker`` containers from the ``docker-compose.yml`` file. -#. Data can be downloaded by making a POST request to the following endpoint ``http://{ip}:{port}/download``. This endpoint will communicate with all participating recorder federates ans retrieve the simulation results in a single zip file, +#. Data can be downloaded by making a POST request to the following endpoint ``http://{ip}:{port}/download``. This endpoint will communicate with all participating recorder federates ans retrieve the simulation results in a single zip file, This will later be simplified so users are able to download all results using a single endpoint fromthe broker container. diff --git a/src/oedisi/componentframework/mock_component.py b/src/oedisi/componentframework/mock_component.py index e947e81..24fe0c2 100644 --- a/src/oedisi/componentframework/mock_component.py +++ b/src/oedisi/componentframework/mock_component.py @@ -56,9 +56,7 @@ def generate_helics_config(self, outputs): "period": 1, "log_level": "warning", "terminate_on_error": True, - "publications": [ - {"key": key, "type": value} for key, value in outputs.items() - ], + "publications": [{"key": key, "type": value} for key, value in outputs.items()], } with open(os.path.join(self._directory, "helics_config.json"), "w") as f: @@ -126,9 +124,7 @@ def run(self): for name, sub in self.subscriptions.items(): if sub.is_updated(): - logger.info( - f"From subscription {name}: {sub.bytes} of type {sub.type}" - ) + logger.info(f"From subscription {name}: {sub.bytes} of type {sub.type}") destroy_federate(self.fed) diff --git a/src/oedisi/componentframework/mock_component.sh b/src/oedisi/componentframework/mock_component.sh index 687e213..5128389 100755 --- a/src/oedisi/componentframework/mock_component.sh +++ b/src/oedisi/componentframework/mock_component.sh @@ -1,4 +1,3 @@ #!/bin/sh python -m oedisi.componentframework.mock_component - diff --git a/src/oedisi/componentframework/system_configuration.py b/src/oedisi/componentframework/system_configuration.py index f4d173d..c230d9b 100644 --- a/src/oedisi/componentframework/system_configuration.py +++ b/src/oedisi/componentframework/system_configuration.py @@ -233,14 +233,14 @@ def initialize_federates( source_types = components[l.source].dynamic_outputs target_types = components[l.target].dynamic_inputs assert l.source_port in source_types, f"{l.source} does not have {l.source_port}" - assert l.target_port in target_types, ( - f"{l.target} does not have dynamic input {l.target_port}" - ) + assert ( + l.target_port in target_types + ), f"{l.target} does not have dynamic input {l.target_port}" source_type = source_types[l.source_port] target_type = target_types[l.target_port] - assert compatability_checker(source_type, target_type), ( - f"{source_type} is not compatible with {target_type}" - ) + assert compatability_checker( + source_type, target_type + ), f"{source_type} is not compatible with {target_type}" federates = [] for name, component in components.items(): diff --git a/src/oedisi/componentframework/wiring_diagram_utils.py b/src/oedisi/componentframework/wiring_diagram_utils.py index e79c924..ad8239d 100644 --- a/src/oedisi/componentframework/wiring_diagram_utils.py +++ b/src/oedisi/componentframework/wiring_diagram_utils.py @@ -16,9 +16,7 @@ def get_graph(wiring_diagram: WiringDiagram): for c in wiring_diagram.components: g.add_node(c.name, type=c.type, parameters=c.parameters) for l in wiring_diagram.links: - g.add_edge( - l.source, l.target, source_port=l.source_port, target_port=l.target_port - ) + g.add_edge(l.source, l.target, source_port=l.source_port, target_port=l.target_port) return g @@ -58,9 +56,7 @@ def get_graph_renderer(G): graph_renderer = from_networkx(G, nx.spectral_layout, scale=1, center=(0, 0)) graph_renderer.node_renderer.glyph = Circle(size=15, fill_color=Spectral4[0]) - graph_renderer.node_renderer.selection_glyph = Circle( - size=15, fill_color=Spectral4[2] - ) + graph_renderer.node_renderer.selection_glyph = Circle(size=15, fill_color=Spectral4[2]) graph_renderer.node_renderer.hover_glyph = Circle(size=15, fill_color=Spectral4[1]) graph_renderer.edge_renderer.glyph = MultiLine( diff --git a/src/oedisi/tools/cli_tools.py b/src/oedisi/tools/cli_tools.py index fe5d653..944ce74 100644 --- a/src/oedisi/tools/cli_tools.py +++ b/src/oedisi/tools/cli_tools.py @@ -54,6 +54,7 @@ def get_basic_component(filename): comp_desc.directory = os.path.dirname(filename) return basic_component(comp_desc, bad_type_checker) + @cli.command() @click.option( "--target-directory", @@ -85,15 +86,12 @@ def get_basic_component(filename): "-p", "--broker-port", default=8766, show_default=True, help="Pass the broker port." ) @click.option( - "-i", "--simulation-id", help="Simulation ID for kubernetres or docker compose configurations." + "-i", + "--simulation-id", + help="Simulation ID for kubernetres or docker compose configurations.", ) def build( - target_directory, - system, - component_dict, - multi_container, - broker_port, - simulation_id + target_directory, system, component_dict, multi_container, broker_port, simulation_id ): """Build to the simulation folder @@ -120,7 +118,6 @@ def build( """ click.echo(f"Loading the components defined in {component_dict}") with open(component_dict) as f: - component_dict_of_files = json.load(f) component_types = { name: get_basic_component(component_file) @@ -133,7 +130,7 @@ def build( click.echo(f"Building system in {target_directory}") - if multi_container: + if multi_container: if simulation_id is None: simulation_id = str(uuid4()) click.echo(f"Simulation ID: {simulation_id}") @@ -141,10 +138,12 @@ def build( if not Path(simulation_dir).exists(): os.makedirs(simulation_dir, exist_ok=True) - + validate_optional_inputs(wiring_diagram, component_dict_of_files) edit_docker_files(wiring_diagram, component_types) - create_docker_compose_file(wiring_diagram, simulation_dir, broker_port, component_types, simulation_id) + create_docker_compose_file( + wiring_diagram, simulation_dir, broker_port, component_types, simulation_id + ) create_kubernetes_deployment( wiring_diagram, simulation_dir, broker_port, simulation_id ) @@ -160,12 +159,12 @@ def build( def validate_optional_inputs(wiring_diagram: WiringDiagram, component_dict_of_files: dict): for component in wiring_diagram.components: - assert hasattr(component, "host"), ( - f"host parameter required for component {component.name} for multi-continer model build" - ) - assert hasattr(component, "container_port"), ( - f"post parameter required for component {component.name} for multi-continer model build" - ) + assert hasattr( + component, "host" + ), f"host parameter required for component {component.name} for multi-continer model build" + assert hasattr( + component, "container_port" + ), f"post parameter required for component {component.name} for multi-continer model build" def drop_null_values(model: Any) -> dict: @@ -195,7 +194,10 @@ def drop_null_values(model: Any) -> dict: def create_kubernetes_deployment( - wiring_diagram: WiringDiagram, target_directory:Path|str, broker_port:int, simulation_id:str + wiring_diagram: WiringDiagram, + target_directory: Path | str, + broker_port: int, + simulation_id: str, ): kube_folder = os.path.join(target_directory, "kubernetes") if not os.path.exists(kube_folder): @@ -206,17 +208,17 @@ def create_kubernetes_deployment( service = client.V1Service( api_version="v1", kind="Service", - metadata= client.V1ObjectMeta( - name = kube_network_svc - ), + metadata=client.V1ObjectMeta(name=kube_network_svc), spec=client.V1ServiceSpec( - type = "NodePort", - selector={"app" : APP_NAME}, - ports = [client.V1ServicePort( - protocol = "TCP", - port = broker_port, - target_port = broker_port, - )] + type="NodePort", + selector={"app": APP_NAME}, + ports=[ + client.V1ServicePort( + protocol="TCP", + port=broker_port, + target_port=broker_port, + ) + ], ), ) @@ -232,10 +234,11 @@ def create_kubernetes_deployment( create_single_kubernetes_deyployment(component, kube_folder, simulation_id) -def create_single_kubernetes_deyployment(component:Component, kube_folder:Path|str, simulation_id:str): - +def create_single_kubernetes_deyployment( + component: Component, kube_folder: Path | str, simulation_id: str +): kube_network_svc = f"{KUBERNETES_SERVICE_PREFIX}-{simulation_id}".lower() - fixed_container_name = component.name.replace("_", "-") + fixed_container_name = component.name.replace("_", "-") my_container = client.V1Container( name=fixed_container_name, image=component.image, @@ -244,7 +247,7 @@ def create_single_kubernetes_deyployment(component:Component, kube_folder:Path|s client.V1EnvVar( name="SERVICE_NAME", value=kube_network_svc, - ) + ), ], ports=[client.V1ContainerPort(container_port=component.container_port)], ) @@ -254,14 +257,14 @@ def create_single_kubernetes_deyployment(component:Component, kube_folder:Path|s kind="Pod", metadata=client.V1ObjectMeta( name=f"{fixed_container_name}-{simulation_id}-pod", - labels ={"app" : APP_NAME}, + labels={"app": APP_NAME}, ), spec=client.V1PodSpec( containers=[my_container], hostname=fixed_container_name, subdomain=kube_network_svc, - ), - ) + ), + ) pod_dict = drop_null_values(pod.to_dict()) with open(os.path.join(kube_folder, f"{component.name}.yml"), "w") as f: @@ -271,16 +274,16 @@ def create_single_kubernetes_deyployment(component:Component, kube_folder:Path|s def edit_docker_file(file_path, component: Component): dir_path = os.path.abspath(os.path.join(file_path, os.pardir)) server_file = os.path.join(dir_path, "server.py") - assert os.path.exists(server_file), ( - f"Server.py file missing for {component.name}.REST API implementation expected in a server.py file" - ) + assert os.path.exists( + server_file + ), f"Server.py file missing for {component.name}.REST API implementation expected in a server.py file" with open(file_path, "w") as f: f.write(f"FROM {BASE_DOCKER_IMAGE}\n") f.write("RUN apt-get update\n") f.write("RUN apt-get install -y git ssh\n") - #TODO: This works for now. Should be removed when a tagged release is available + # TODO: This works for now. Should be removed when a tagged release is available f.write("RUN git clone https://github.com/openEDI/oedisi.git /oedisi\n") f.write("RUN pip install /oedisi \n") @@ -306,7 +309,11 @@ def edit_docker_files(wiring_diagram: WiringDiagram, component_types: dict): def create_docker_compose_file( - wiring_diagram: WiringDiagram, target_directory: str, broker_port: int, component_types: dict, simulation_id: str + wiring_diagram: WiringDiagram, + target_directory: str, + broker_port: int, + component_types: dict, + simulation_id: str, ): config = {"services": {}, "networks": {}} @@ -432,16 +439,16 @@ def run_mc(runner, kubernetes, docker_compose): os.system("docker system prune --all") os.system("docker network prune --all") if docker_compose: - assert file_name == "docker-compose.yml", ( - f"{file_name} is not a valid docker-compose.yml file" - ) + assert ( + file_name == "docker-compose.yml" + ), f"{file_name} is not a valid docker-compose.yml file" build_path = os.path.dirname(os.path.abspath(runner)) os.chdir(build_path) os.system("docker-compose up") elif kubernetes: - assert file_name == "deployment.yml", ( - f"{file_name} is not a valid deployment.yml file for kubernetes." - ) + assert ( + file_name == "deployment.yml" + ), f"{file_name} is not a valid deployment.yml file for kubernetes." build_path = os.path.dirname(os.path.abspath(runner)) os.system(f"kubectl apply -f {build_path}") else: @@ -550,18 +557,18 @@ def test_description(target_directory, component_desc, parameters): actual_inputs = sorted( list(map(lambda x: x.split("/")[1], federate_inputs["component"])) ) - assert expected_inputs == actual_inputs, ( - f"Input mismatch: expected {expected_inputs}, got {actual_inputs}" - ) + assert ( + expected_inputs == actual_inputs + ), f"Input mismatch: expected {expected_inputs}, got {actual_inputs}" print("✓") print("Testing dynamic output names") expected_outputs = sorted( list(map(lambda x: "component/" + x.port_name, comp_desc.dynamic_outputs)) ) actual_outputs = sorted(federate_outputs["component"]) - assert expected_outputs == actual_outputs, ( - f"Output mismatch: expected {expected_outputs}, got {actual_outputs}" - ) + assert ( + expected_outputs == actual_outputs + ), f"Output mismatch: expected {expected_outputs}, got {actual_outputs}" print("✓") diff --git a/src/oedisi/tools/metrics.py b/src/oedisi/tools/metrics.py index ea7eca5..a9a99c3 100644 --- a/src/oedisi/tools/metrics.py +++ b/src/oedisi/tools/metrics.py @@ -84,15 +84,9 @@ def evaluate_estimate(path, metric, angle_unit): estimated_time = estimated_magnitude["time"] common_time = set(time).intersection(estimated_time) - true_voltages_real = true_voltages_real[ - true_voltages_real["time"].isin(common_time) - ] - true_voltages_imag = true_voltages_imag[ - true_voltages_imag["time"].isin(common_time) - ] - estimated_magnitude = estimated_magnitude[ - estimated_magnitude["time"].isin(common_time) - ] + true_voltages_real = true_voltages_real[true_voltages_real["time"].isin(common_time)] + true_voltages_imag = true_voltages_imag[true_voltages_imag["time"].isin(common_time)] + estimated_magnitude = estimated_magnitude[estimated_magnitude["time"].isin(common_time)] estimated_angle = estimated_angle[estimated_angle["time"].isin(common_time)] estimated_magnitude = estimated_magnitude.groupby("time").last().reset_index() @@ -101,19 +95,13 @@ def evaluate_estimate(path, metric, angle_unit): assert list(true_voltages_real["time"]) == list( estimated_magnitude["time"] ), f"""Time does not match between true voltages and estimated magnitudes: - + {list(true_voltages_real["time"])} vs {list(estimated_magnitude["time"])}""" # Strip time column from voltages - true_voltages_real = true_voltages_real.drop(columns=["time"]).reset_index( - drop=True - ) - true_voltages_imag = true_voltages_imag.drop(columns=["time"]).reset_index( - drop=True - ) - estimated_magnitude = estimated_magnitude.drop(columns=["time"]).reset_index( - drop=True - ) + true_voltages_real = true_voltages_real.drop(columns=["time"]).reset_index(drop=True) + true_voltages_imag = true_voltages_imag.drop(columns=["time"]).reset_index(drop=True) + estimated_magnitude = estimated_magnitude.drop(columns=["time"]).reset_index(drop=True) estimated_angle = estimated_angle.drop(columns=["time"]).reset_index(drop=True) if angle_unit == "degrees": # convert to radians estimated_angle = estimated_angle * np.pi / 180 diff --git a/src/oedisi/tools/testing_broker.py b/src/oedisi/tools/testing_broker.py index 7e73187..acac949 100644 --- a/src/oedisi/tools/testing_broker.py +++ b/src/oedisi/tools/testing_broker.py @@ -54,7 +54,5 @@ def wait_until_connected(self): time.sleep(2) current_state = self.broker.query("broker", "current_state") cores = current_state["cores"] - if len(cores) == 2 and all( - core["state"] == "init_requested" for core in cores - ): + if len(cores) == 2 and all(core["state"] == "init_requested" for core in cores): return diff --git a/src/oedisi/types/common.py b/src/oedisi/types/common.py index 12a6b3d..ad75e13 100644 --- a/src/oedisi/types/common.py +++ b/src/oedisi/types/common.py @@ -5,7 +5,7 @@ BROKER_SERVICE = "broker" APP_NAME = "oedisi" DOCKER_HUB_USER = "aadillatif" -KUBERNETES_SERVICE_PREFIX= "svc" +KUBERNETES_SERVICE_PREFIX = "svc" class DefaultFileNames(str, Enum): diff --git a/src/oedisi/types/schemas/AdmittanceMatrix.json b/src/oedisi/types/schemas/AdmittanceMatrix.json index 243214c..26b8d2a 100644 --- a/src/oedisi/types/schemas/AdmittanceMatrix.json +++ b/src/oedisi/types/schemas/AdmittanceMatrix.json @@ -39,4 +39,4 @@ ], "title": "AdmittanceMatrix", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/AdmittanceSparse.json b/src/oedisi/types/schemas/AdmittanceSparse.json index f213b8b..eeb9c15 100644 --- a/src/oedisi/types/schemas/AdmittanceSparse.json +++ b/src/oedisi/types/schemas/AdmittanceSparse.json @@ -59,4 +59,4 @@ ], "title": "AdmittanceSparse", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/CapacitorStates.json b/src/oedisi/types/schemas/CapacitorStates.json index 52ff277..78316a3 100644 --- a/src/oedisi/types/schemas/CapacitorStates.json +++ b/src/oedisi/types/schemas/CapacitorStates.json @@ -34,4 +34,4 @@ ], "title": "CapacitorStates", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/Command.json b/src/oedisi/types/schemas/Command.json index 9fb2dde..92bd365 100644 --- a/src/oedisi/types/schemas/Command.json +++ b/src/oedisi/types/schemas/Command.json @@ -21,4 +21,4 @@ ], "title": "Command", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/CommandList.json b/src/oedisi/types/schemas/CommandList.json index 3d8d3ae..df2e769 100644 --- a/src/oedisi/types/schemas/CommandList.json +++ b/src/oedisi/types/schemas/CommandList.json @@ -31,4 +31,4 @@ ] } } -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/CostArray.json b/src/oedisi/types/schemas/CostArray.json index b06023a..274a38b 100644 --- a/src/oedisi/types/schemas/CostArray.json +++ b/src/oedisi/types/schemas/CostArray.json @@ -43,4 +43,4 @@ ], "title": "CostArray", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/CurrentsAngle.json b/src/oedisi/types/schemas/CurrentsAngle.json index 796501d..bd54cea 100644 --- a/src/oedisi/types/schemas/CurrentsAngle.json +++ b/src/oedisi/types/schemas/CurrentsAngle.json @@ -69,4 +69,4 @@ ], "title": "CurrentsAngle", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/CurrentsImaginary.json b/src/oedisi/types/schemas/CurrentsImaginary.json index dce1f9e..6aa6caa 100644 --- a/src/oedisi/types/schemas/CurrentsImaginary.json +++ b/src/oedisi/types/schemas/CurrentsImaginary.json @@ -69,4 +69,4 @@ ], "title": "CurrentsImaginary", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/CurrentsMagnitude.json b/src/oedisi/types/schemas/CurrentsMagnitude.json index d801d09..c328b72 100644 --- a/src/oedisi/types/schemas/CurrentsMagnitude.json +++ b/src/oedisi/types/schemas/CurrentsMagnitude.json @@ -69,4 +69,4 @@ ], "title": "CurrentsMagnitude", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/CurrentsReal.json b/src/oedisi/types/schemas/CurrentsReal.json index 166a754..856335a 100644 --- a/src/oedisi/types/schemas/CurrentsReal.json +++ b/src/oedisi/types/schemas/CurrentsReal.json @@ -69,4 +69,4 @@ ], "title": "CurrentsReal", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/Injection.json b/src/oedisi/types/schemas/Injection.json index e7d264a..b8e7601 100644 --- a/src/oedisi/types/schemas/Injection.json +++ b/src/oedisi/types/schemas/Injection.json @@ -501,4 +501,4 @@ }, "title": "Injection", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/InverterControl.json b/src/oedisi/types/schemas/InverterControl.json index 2cf104d..54211ab 100644 --- a/src/oedisi/types/schemas/InverterControl.json +++ b/src/oedisi/types/schemas/InverterControl.json @@ -141,4 +141,4 @@ }, "title": "InverterControl", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/InverterControlList.json b/src/oedisi/types/schemas/InverterControlList.json index 905aa0d..bfd1370 100644 --- a/src/oedisi/types/schemas/InverterControlList.json +++ b/src/oedisi/types/schemas/InverterControlList.json @@ -131,4 +131,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/MeasurementArray.json b/src/oedisi/types/schemas/MeasurementArray.json index 09d6bd9..0c64c6c 100644 --- a/src/oedisi/types/schemas/MeasurementArray.json +++ b/src/oedisi/types/schemas/MeasurementArray.json @@ -70,4 +70,4 @@ ], "title": "MeasurementArray", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/OperationalCosts.json b/src/oedisi/types/schemas/OperationalCosts.json index 5cc8138..39b959e 100644 --- a/src/oedisi/types/schemas/OperationalCosts.json +++ b/src/oedisi/types/schemas/OperationalCosts.json @@ -42,4 +42,4 @@ ], "title": "OperationalCosts", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/PowersAngle.json b/src/oedisi/types/schemas/PowersAngle.json index 3dfb0af..4eba3b7 100644 --- a/src/oedisi/types/schemas/PowersAngle.json +++ b/src/oedisi/types/schemas/PowersAngle.json @@ -77,4 +77,4 @@ ], "title": "PowersAngle", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/PowersImaginary.json b/src/oedisi/types/schemas/PowersImaginary.json index 421628c..a659a3a 100644 --- a/src/oedisi/types/schemas/PowersImaginary.json +++ b/src/oedisi/types/schemas/PowersImaginary.json @@ -77,4 +77,4 @@ ], "title": "PowersImaginary", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/PowersMagnitude.json b/src/oedisi/types/schemas/PowersMagnitude.json index 745e381..997af1a 100644 --- a/src/oedisi/types/schemas/PowersMagnitude.json +++ b/src/oedisi/types/schemas/PowersMagnitude.json @@ -77,4 +77,4 @@ ], "title": "PowersMagnitude", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/PowersReal.json b/src/oedisi/types/schemas/PowersReal.json index 60ef484..57ca2ef 100644 --- a/src/oedisi/types/schemas/PowersReal.json +++ b/src/oedisi/types/schemas/PowersReal.json @@ -77,4 +77,4 @@ ], "title": "PowersReal", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/ReactiveCostFunctions.json b/src/oedisi/types/schemas/ReactiveCostFunctions.json index ba85cbb..92186b4 100644 --- a/src/oedisi/types/schemas/ReactiveCostFunctions.json +++ b/src/oedisi/types/schemas/ReactiveCostFunctions.json @@ -42,4 +42,4 @@ ], "title": "ReactiveCostFunctions", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/ReactiveWholesalePrices.json b/src/oedisi/types/schemas/ReactiveWholesalePrices.json index 3910df3..4b537ff 100644 --- a/src/oedisi/types/schemas/ReactiveWholesalePrices.json +++ b/src/oedisi/types/schemas/ReactiveWholesalePrices.json @@ -42,4 +42,4 @@ ], "title": "ReactiveWholesalePrices", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/RealCostFunctions.json b/src/oedisi/types/schemas/RealCostFunctions.json index b941444..62962ed 100644 --- a/src/oedisi/types/schemas/RealCostFunctions.json +++ b/src/oedisi/types/schemas/RealCostFunctions.json @@ -42,4 +42,4 @@ ], "title": "RealCostFunctions", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/RealWholesalePrices.json b/src/oedisi/types/schemas/RealWholesalePrices.json index 4746b8e..a462387 100644 --- a/src/oedisi/types/schemas/RealWholesalePrices.json +++ b/src/oedisi/types/schemas/RealWholesalePrices.json @@ -42,4 +42,4 @@ ], "title": "RealWholesalePrices", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/RegulatorStates.json b/src/oedisi/types/schemas/RegulatorStates.json index c9a70f6..2c0361b 100644 --- a/src/oedisi/types/schemas/RegulatorStates.json +++ b/src/oedisi/types/schemas/RegulatorStates.json @@ -34,4 +34,4 @@ ], "title": "RegulatorStates", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/RootModel[List[Command]].json b/src/oedisi/types/schemas/RootModel[List[Command]].json index 7ad5b5e..f45f0e6 100644 --- a/src/oedisi/types/schemas/RootModel[List[Command]].json +++ b/src/oedisi/types/schemas/RootModel[List[Command]].json @@ -30,4 +30,4 @@ }, "title": "RootModel[List[Command]]", "type": "array" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/RootModel[List[InverterControl]].json b/src/oedisi/types/schemas/RootModel[List[InverterControl]].json index f07e193..2f03e2d 100644 --- a/src/oedisi/types/schemas/RootModel[List[InverterControl]].json +++ b/src/oedisi/types/schemas/RootModel[List[InverterControl]].json @@ -148,4 +148,4 @@ }, "title": "RootModel[List[InverterControl]]", "type": "array" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/SolarIrradiances.json b/src/oedisi/types/schemas/SolarIrradiances.json index d1c900a..79c9f39 100644 --- a/src/oedisi/types/schemas/SolarIrradiances.json +++ b/src/oedisi/types/schemas/SolarIrradiances.json @@ -69,4 +69,4 @@ ], "title": "SolarIrradiances", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/StateArray.json b/src/oedisi/types/schemas/StateArray.json index e7c9c3a..1deddb2 100644 --- a/src/oedisi/types/schemas/StateArray.json +++ b/src/oedisi/types/schemas/StateArray.json @@ -35,4 +35,4 @@ ], "title": "StateArray", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/StatesOfCharge.json b/src/oedisi/types/schemas/StatesOfCharge.json index 5c25dbf..5a4e8b4 100644 --- a/src/oedisi/types/schemas/StatesOfCharge.json +++ b/src/oedisi/types/schemas/StatesOfCharge.json @@ -69,4 +69,4 @@ ], "title": "StatesOfCharge", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/SwitchStates.json b/src/oedisi/types/schemas/SwitchStates.json index b6dcd6b..8998fd9 100644 --- a/src/oedisi/types/schemas/SwitchStates.json +++ b/src/oedisi/types/schemas/SwitchStates.json @@ -34,4 +34,4 @@ ], "title": "SwitchStates", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/Temperatures.json b/src/oedisi/types/schemas/Temperatures.json index 7916cf9..adea1c3 100644 --- a/src/oedisi/types/schemas/Temperatures.json +++ b/src/oedisi/types/schemas/Temperatures.json @@ -69,4 +69,4 @@ ], "title": "Temperatures", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/Topology.json b/src/oedisi/types/schemas/Topology.json index b0f0950..393c41a 100644 --- a/src/oedisi/types/schemas/Topology.json +++ b/src/oedisi/types/schemas/Topology.json @@ -861,4 +861,4 @@ ], "title": "Topology", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/VVControl.json b/src/oedisi/types/schemas/VVControl.json index ca73493..3470af1 100644 --- a/src/oedisi/types/schemas/VVControl.json +++ b/src/oedisi/types/schemas/VVControl.json @@ -53,4 +53,4 @@ ], "title": "VVControl", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/VWControl.json b/src/oedisi/types/schemas/VWControl.json index a16c25f..fce4935 100644 --- a/src/oedisi/types/schemas/VWControl.json +++ b/src/oedisi/types/schemas/VWControl.json @@ -27,4 +27,4 @@ ], "title": "VWControl", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/VoltagesAngle.json b/src/oedisi/types/schemas/VoltagesAngle.json index 455e9f9..cc5b2c2 100644 --- a/src/oedisi/types/schemas/VoltagesAngle.json +++ b/src/oedisi/types/schemas/VoltagesAngle.json @@ -69,4 +69,4 @@ ], "title": "VoltagesAngle", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/VoltagesImaginary.json b/src/oedisi/types/schemas/VoltagesImaginary.json index d0f21af..6cb14e6 100644 --- a/src/oedisi/types/schemas/VoltagesImaginary.json +++ b/src/oedisi/types/schemas/VoltagesImaginary.json @@ -69,4 +69,4 @@ ], "title": "VoltagesImaginary", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/VoltagesMagnitude.json b/src/oedisi/types/schemas/VoltagesMagnitude.json index 7954294..f292ea6 100644 --- a/src/oedisi/types/schemas/VoltagesMagnitude.json +++ b/src/oedisi/types/schemas/VoltagesMagnitude.json @@ -69,4 +69,4 @@ ], "title": "VoltagesMagnitude", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/VoltagesReal.json b/src/oedisi/types/schemas/VoltagesReal.json index be3e5f7..6db6ae3 100644 --- a/src/oedisi/types/schemas/VoltagesReal.json +++ b/src/oedisi/types/schemas/VoltagesReal.json @@ -69,4 +69,4 @@ ], "title": "VoltagesReal", "type": "object" -} \ No newline at end of file +} diff --git a/src/oedisi/types/schemas/WindSpeeds.json b/src/oedisi/types/schemas/WindSpeeds.json index 24c8433..7e30f14 100644 --- a/src/oedisi/types/schemas/WindSpeeds.json +++ b/src/oedisi/types/schemas/WindSpeeds.json @@ -69,4 +69,4 @@ ], "title": "WindSpeeds", "type": "object" -} \ No newline at end of file +} diff --git a/tests/test_basic_system/test_component_type/test_component_type.py b/tests/test_basic_system/test_component_type/test_component_type.py index c89f4a5..251e1c3 100644 --- a/tests/test_basic_system/test_component_type/test_component_type.py +++ b/tests/test_basic_system/test_component_type/test_component_type.py @@ -1,5 +1,4 @@ -""" -""" +""" """ import helics as h import logging @@ -67,9 +66,7 @@ def run(self): for name, sub in self.subscriptions.items(): if sub.is_updated(): - logger.info( - f"From subscription {name}: {sub.bytes} of type {sub.type}" - ) + logger.info(f"From subscription {name}: {sub.bytes} of type {sub.type}") destroy_federate(self.fed) diff --git a/tests/unit_tests/test_data_types/data/Topology.1.json b/tests/unit_tests/test_data_types/data/Topology.1.json index 69fdc74..3f0006e 100644 --- a/tests/unit_tests/test_data_types/data/Topology.1.json +++ b/tests/unit_tests/test_data_types/data/Topology.1.json @@ -78,4 +78,4 @@ "slack_bus": [ "node1" ] -} \ No newline at end of file +} diff --git a/tests/unit_tests/test_data_types/data/Topology.2.json b/tests/unit_tests/test_data_types/data/Topology.2.json index 7fc9662..84d9a09 100644 --- a/tests/unit_tests/test_data_types/data/Topology.2.json +++ b/tests/unit_tests/test_data_types/data/Topology.2.json @@ -152,4 +152,4 @@ "slack_bus": [ "node1" ] -} \ No newline at end of file +} diff --git a/tests/unit_tests/test_oedisi_tools/broker/Dockerfile b/tests/unit_tests/test_oedisi_tools/broker/Dockerfile index 9c37e21..da72880 100644 --- a/tests/unit_tests/test_oedisi_tools/broker/Dockerfile +++ b/tests/unit_tests/test_oedisi_tools/broker/Dockerfile @@ -1,9 +1,9 @@ FROM python:3.13-slim-bullseye -RUN apt-get update +RUN apt-get update RUN apt-get install -y git ssh RUN git clone https://github.com/openEDI/oedisi.git /oedisi -RUN pip install /oedisi +RUN pip install /oedisi RUN mkdir broker COPY * ./broker WORKDIR ./broker diff --git a/tests/unit_tests/test_oedisi_tools/broker/requirements.txt b/tests/unit_tests/test_oedisi_tools/broker/requirements.txt index 911cffb..bb8b360 100644 --- a/tests/unit_tests/test_oedisi_tools/broker/requirements.txt +++ b/tests/unit_tests/test_oedisi_tools/broker/requirements.txt @@ -6,4 +6,3 @@ fastapi uvicorn httpx python-multipart - diff --git a/tests/unit_tests/test_oedisi_tools/broker/server.py b/tests/unit_tests/test_oedisi_tools/broker/server.py index 39cca83..822236d 100644 --- a/tests/unit_tests/test_oedisi_tools/broker/server.py +++ b/tests/unit_tests/test_oedisi_tools/broker/server.py @@ -33,6 +33,7 @@ WIRING_DIAGRAM_FILENAME = "system.json" WIRING_DIAGRAM: WiringDiagram | None = None + def read_settings(): broker_host = socket.gethostname() s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -48,7 +49,10 @@ def read_settings(): for component in WIRING_DIAGRAM.components: component_map[component.host] = component.container_port else: - raise HTTPException(status_code=404, detail="Use the '/configure' setpoint to setup up the WiringDiagram before making requests other enpoints") + raise HTTPException( + status_code=404, + detail="Use the '/configure' setpoint to setup up the WiringDiagram before making requests other enpoints", + ) return component_map, broker_ip, api_port @@ -56,12 +60,13 @@ def read_settings(): @cache def kubernetes_service(): if "KUBERNETES_SERVICE_NAME" in os.environ: - return os.environ["KUBERNETES_SERVICE_NAME"] # works with kurenetes + return os.environ["KUBERNETES_SERVICE_NAME"] # works with kurenetes elif "SERVICE_NAME" in os.environ: - return os.environ["SERVICE_NAME"] # works with minikube + return os.environ["SERVICE_NAME"] # works with minikube else: return None + def build_url(host: str, port: int, enpoint: list): if kubernetes_service(): url = f"http://{host}.{kubernetes_service()}:{port}/" @@ -70,8 +75,8 @@ def build_url(host: str, port: int, enpoint: list): url = url + "/".join(enpoint) + "/" return url + async def run_simulation(): - component_map, broker_ip, api_port = read_settings() logger.info(f"{broker_ip}, {api_port}") initstring = f"-f {len(component_map)-1} --name=mainbroker --loglevel=trace --local_interface={broker_ip} --localport=23404" @@ -109,11 +114,7 @@ async def run_simulation(): done = {t for t in pending if t.done()} for idx, t in enumerate(tasks): state = ( - "done" - if t.done() - else "cancelled" - if t.cancelled() - else "pending" + "done" if t.done() else "cancelled" if t.cancelled() else "pending" ) info = None if t.done() and not t.cancelled(): @@ -134,12 +135,14 @@ async def run_simulation(): for idx, t in enumerate(tasks): try: res = t.result() - logger.info(f"Task {idx} succeeded: {getattr(res, 'status_code', 'N/A')}") + logger.info( + f"Task {idx} succeeded: {getattr(res, 'status_code', 'N/A')}" + ) except Exception as exc: logger.error(f"Task {idx} failed: {exc}") while h.helicsBrokerIsConnected(broker): - time.sleep(1) + time.sleep(1) query_result = broker.query("broker", "current_state") logger.info(f"Federates expected: {len(component_map)-1}") logger.info(f"Federates connected: {len(broker.query("broker", "federates"))}") @@ -148,6 +151,7 @@ async def run_simulation(): h.helicsCloseLibrary() return + @app.get("/") def read_root(): hostname = socket.gethostname() @@ -193,5 +197,6 @@ async def configure(wiring_diagram: WiringDiagram): 200, ) + if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=int(os.environ["PORT"])) diff --git a/tests/unit_tests/test_oedisi_tools/component1/component1.py b/tests/unit_tests/test_oedisi_tools/component1/component1.py index 32d2528..6110481 100644 --- a/tests/unit_tests/test_oedisi_tools/component1/component1.py +++ b/tests/unit_tests/test_oedisi_tools/component1/component1.py @@ -1,5 +1,5 @@ -""" -""" +""" """ + from oedisi.types.common import BrokerConfig import helics as h import logging @@ -28,7 +28,9 @@ def __init__(self, broker_config: BrokerConfig = BrokerConfig()): fedinfo = h.helicsCreateFederateInfo() h.helicsFederateInfoSetBroker(fedinfo, broker_config.broker_ip) h.helicsFederateInfoSetBrokerPort(fedinfo, broker_config.broker_port) - logger.info(f"Federate connected to {broker_config.broker_ip}@{broker_config.broker_port}") + logger.info( + f"Federate connected to {broker_config.broker_ip}@{broker_config.broker_port}" + ) fedinfo.core_name = self.parameters["name"] fedinfo.core_type = h.HELICS_CORE_TYPE_ZMQ fedinfo.core_init = "--federates=1 --loglevel=trace" @@ -70,9 +72,7 @@ def run(self): for name, sub in self.subscriptions.items(): if sub.is_updated(): - logger.info( - f"From subscription {name}: {sub.bytes} of type {sub.type}" - ) + logger.info(f"From subscription {name}: {sub.bytes} of type {sub.type}") destroy_federate(self.fed) diff --git a/tests/unit_tests/test_oedisi_tools/component1/component_definition.json b/tests/unit_tests/test_oedisi_tools/component1/component_definition.json index 6348132..6ce6bf7 100644 --- a/tests/unit_tests/test_oedisi_tools/component1/component_definition.json +++ b/tests/unit_tests/test_oedisi_tools/component1/component_definition.json @@ -9,4 +9,4 @@ {"type": "", "port_id": "test2"} ], "apiversion": "1.0.0" -} \ No newline at end of file +} diff --git a/tests/unit_tests/test_oedisi_tools/component1/requirements.txt b/tests/unit_tests/test_oedisi_tools/component1/requirements.txt index 3b60b6a..7ef9a00 100644 --- a/tests/unit_tests/test_oedisi_tools/component1/requirements.txt +++ b/tests/unit_tests/test_oedisi_tools/component1/requirements.txt @@ -2,4 +2,4 @@ helics oedisi>=2.0.2,<3 pydantic fastapi -uvicorn \ No newline at end of file +uvicorn diff --git a/tests/unit_tests/test_oedisi_tools/component1/server.py b/tests/unit_tests/test_oedisi_tools/component1/server.py index 81bd9c8..56237ff 100644 --- a/tests/unit_tests/test_oedisi_tools/component1/server.py +++ b/tests/unit_tests/test_oedisi_tools/component1/server.py @@ -18,6 +18,7 @@ logger = logging.getLogger("uvicorn.error") logger.setLevel(logging.DEBUG) + @app.get("/") def read_root(): hostname = socket.gethostname() @@ -46,21 +47,20 @@ async def run_model(broker_config: BrokerConfig, background_tasks: BackgroundTas @app.post("/configure/") -async def configure(component_struct:ComponentStruct): +async def configure(component_struct: ComponentStruct): component = component_struct.component params = component.parameters params["name"] = component.name links = {} for link in component_struct.links: links[link.target_port] = f"{link.source}/{link.source_port}" - with open(DefaultFileNames.INPUT_MAPPING.value, "w") as f: + with open(DefaultFileNames.INPUT_MAPPING.value, "w") as f: json.dump(links, f) with open(DefaultFileNames.STATIC_INPUTS.value, "w") as f: json.dump(params, f) - response = ServerReply( - detail = "Sucessfully updated configuration files." - ).model_dump() + response = ServerReply(detail="Sucessfully updated configuration files.").model_dump() return JSONResponse(response, 200) + if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=int(os.environ["PORT"])) diff --git a/tests/unit_tests/test_oedisi_tools/component2/component2.py b/tests/unit_tests/test_oedisi_tools/component2/component2.py index 68b9642..8b156c4 100644 --- a/tests/unit_tests/test_oedisi_tools/component2/component2.py +++ b/tests/unit_tests/test_oedisi_tools/component2/component2.py @@ -1,5 +1,4 @@ -""" -""" +""" """ from pathlib import Path @@ -25,8 +24,6 @@ def destroy_federate(fed): class TestFederate: def __init__(self, broker_config: BrokerConfig = BrokerConfig()): - - logger.info(f"Current Working Directory: {os.path.abspath(os.curdir)}") with open(BASE_PATH / DefaultFileNames.STATIC_INPUTS) as f: self.parameters = json.load(f) @@ -34,7 +31,9 @@ def __init__(self, broker_config: BrokerConfig = BrokerConfig()): fedinfo = h.helicsCreateFederateInfo() h.helicsFederateInfoSetBroker(fedinfo, broker_config.broker_ip) h.helicsFederateInfoSetBrokerPort(fedinfo, broker_config.broker_port) - logger.info(f"Federate connected to {broker_config.broker_ip}@{broker_config.broker_port}") + logger.info( + f"Federate connected to {broker_config.broker_ip}@{broker_config.broker_port}" + ) fedinfo.core_name = self.parameters["name"] fedinfo.core_type = h.HELICS_CORE_TYPE_ZMQ @@ -77,9 +76,7 @@ def run(self): for name, sub in self.subscriptions.items(): if sub.is_updated(): - logger.info( - f"From subscription {name}: {sub.bytes} of type {sub.type}" - ) + logger.info(f"From subscription {name}: {sub.bytes} of type {sub.type}") destroy_federate(self.fed) diff --git a/tests/unit_tests/test_oedisi_tools/component2/component_definition.json b/tests/unit_tests/test_oedisi_tools/component2/component_definition.json index a050e29..713948b 100644 --- a/tests/unit_tests/test_oedisi_tools/component2/component_definition.json +++ b/tests/unit_tests/test_oedisi_tools/component2/component_definition.json @@ -9,4 +9,4 @@ {"type": "", "port_id": "test2"} ], "apiversion": "1.0.0" -} \ No newline at end of file +} diff --git a/tests/unit_tests/test_oedisi_tools/component2/requirements.txt b/tests/unit_tests/test_oedisi_tools/component2/requirements.txt index f073e44..7ef9a00 100644 --- a/tests/unit_tests/test_oedisi_tools/component2/requirements.txt +++ b/tests/unit_tests/test_oedisi_tools/component2/requirements.txt @@ -3,4 +3,3 @@ oedisi>=2.0.2,<3 pydantic fastapi uvicorn - diff --git a/tests/unit_tests/test_oedisi_tools/component2/server.py b/tests/unit_tests/test_oedisi_tools/component2/server.py index 92ec499..8815aca 100644 --- a/tests/unit_tests/test_oedisi_tools/component2/server.py +++ b/tests/unit_tests/test_oedisi_tools/component2/server.py @@ -36,7 +36,6 @@ def read_root(): return JSONResponse(response, 200) - @app.post("/run") async def run_model(broker_config: BrokerConfig, background_tasks: BackgroundTasks): logger.info("Running componenet 2") @@ -49,24 +48,22 @@ async def run_model(broker_config: BrokerConfig, background_tasks: BackgroundTas return {"reply": "success", "error": False} except Exception: err = traceback.format_exc() - raise HTTPException(500,str(err)) + raise HTTPException(500, str(err)) @app.post("/configure/") -async def configure(component_struct:ComponentStruct): +async def configure(component_struct: ComponentStruct): component = component_struct.component params = component.parameters params["name"] = component.name links = {} for link in component_struct.links: links[link.target_port] = f"{link.source}/{link.source_port}" - with open(DefaultFileNames.INPUT_MAPPING.value, "w") as f: + with open(DefaultFileNames.INPUT_MAPPING.value, "w") as f: json.dump(links, f) with open(DefaultFileNames.STATIC_INPUTS.value, "w") as f: json.dump(params, f) - response = ServerReply( - detail = "Sucessfully updated configuration files." - ).model_dump() + response = ServerReply(detail="Sucessfully updated configuration files.").model_dump() return JSONResponse(response, 200) diff --git a/tests/unit_tests/test_oedisi_tools/component3/component3.py b/tests/unit_tests/test_oedisi_tools/component3/component3.py index c4b70c3..c046b82 100644 --- a/tests/unit_tests/test_oedisi_tools/component3/component3.py +++ b/tests/unit_tests/test_oedisi_tools/component3/component3.py @@ -1,5 +1,4 @@ -""" -""" +""" """ import helics as h import logging @@ -60,9 +59,7 @@ def run(self): for name, sub in self.subscriptions.items(): if sub.is_updated(): - logger.info( - f"From subscription {name}: {sub.bytes} of type {sub.type}" - ) + logger.info(f"From subscription {name}: {sub.bytes} of type {sub.type}") destroy_federate(self.fed) diff --git a/tests/unit_tests/test_oedisi_tools/test_multi_container.py b/tests/unit_tests/test_oedisi_tools/test_multi_container.py index b46acb1..16420fc 100644 --- a/tests/unit_tests/test_oedisi_tools/test_multi_container.py +++ b/tests/unit_tests/test_oedisi_tools/test_multi_container.py @@ -23,6 +23,7 @@ TEST_SIMULATION = "test_sim" IN_GITHUB_ACTIONS = os.getenv("GITHUB_ACTIONS") == "true" + @pytest.fixture def base_path() -> Path: """Get the current folder of the test""" @@ -35,39 +36,36 @@ def test_mc_build(base_path: Path, monkeypatch: pytest.MonkeyPatch): runner = CliRunner() broker_path = base_path / BROKER_SERVICE - assert broker_path.exists(), ( - "Broker federate should be implemented before building a multicontainer problem." - ) + assert ( + broker_path.exists() + ), "Broker federate should be implemented before building a multicontainer problem." api_implementation = broker_path / API_FILE - assert api_implementation.exists(), ( - f"A valid REST API implementatiion should exist in {api_implementation} before building a multicontainer problem." - ) + assert api_implementation.exists(), f"A valid REST API implementatiion should exist in {api_implementation} before building a multicontainer problem." requirements_file = broker_path / "requirements.txt" - assert requirements_file.exists(), ( - "All components should have a valid requirements.txt file listing required python packages for the build." - ) + assert requirements_file.exists(), "All components should have a valid requirements.txt file listing required python packages for the build." result = runner.invoke(cli, ["build", "-m", "-i", TEST_SIMULATION]) assert result.exit_code == 0 - + @pytest.mark.usefixtures("test_mc_build") def test_api_heath_endpoint(base_path: Path, monkeypatch: pytest.MonkeyPatch): build_path = base_path / "build" / TEST_SIMULATION - - assert build_path.exists(), "Build path for the test project does not exist." - assert (build_path / "docker-compose.yml").exists(), "Build path for the test project does not exist." + assert build_path.exists(), "Build path for the test project does not exist." + assert ( + build_path / "docker-compose.yml" + ).exists(), "Build path for the test project does not exist." for folder in base_path.iterdir(): if folder.is_dir(): print(folder.name) if folder.name in ["component1", "component2", "broker"]: - assert (folder / "server.py").exists(), ( - f"Server.py does not exist for path {folder}" - ) + assert ( + folder / "server.py" + ).exists(), f"Server.py does not exist for path {folder}" monkeypatch.syspath_prepend(folder.absolute()) module = importlib.import_module("server") app = getattr(module, "app") @@ -76,15 +74,17 @@ def test_api_heath_endpoint(base_path: Path, monkeypatch: pytest.MonkeyPatch): assert response.status_code == 200 HeathCheck.model_validate(response.json()) else: - assert not (folder / "server.py").exists() + assert not (folder / "server.py").exists() @pytest.mark.usefixtures("test_mc_build") def test_api_run(base_path: Path, monkeypatch: pytest.MonkeyPatch): build_path = base_path / "build" / TEST_SIMULATION - + assert build_path.exists(), "Build path for the test project does not exist." - assert (build_path / "docker-compose.yml").exists(), "Build path for the test project does not exist." + assert ( + build_path / "docker-compose.yml" + ).exists(), "Build path for the test project does not exist." payload = json.load(open(base_path / "system.json")) wiring_diagram = WiringDiagram(**payload) @@ -93,24 +93,26 @@ def test_api_run(base_path: Path, monkeypatch: pytest.MonkeyPatch): for folder in base_path.iterdir(): if folder.is_dir(): if folder.name in ["component1", "component2", "broker"]: - assert (folder / "server.py").exists(), ( - f"Server.py does not exist for path {folder}" - ) + assert ( + folder / "server.py" + ).exists(), f"Server.py does not exist for path {folder}" monkeypatch.syspath_prepend(folder.absolute()) print("folder: ", folder) print("Current dir:", os.getcwd()) module_name = f"{folder.name}_server" - spec = importlib.util.spec_from_file_location(module_name, folder / "server.py") + spec = importlib.util.spec_from_file_location( + module_name, folder / "server.py" + ) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) app = getattr(module, "app") client = TestClient(app) clients[folder.name] = client - assert BROKER_SERVICE in clients, ( - f"No broker client in list of tested services. Available services {list(clients.keys())}" - ) + assert ( + BROKER_SERVICE in clients + ), f"No broker client in list of tested services. Available services {list(clients.keys())}" client = clients[BROKER_SERVICE] # Connection error is raised as the broker running, but not all components are up yet. # wheb broker make s post request to components, they are not available yet. @@ -120,6 +122,7 @@ def test_api_run(base_path: Path, monkeypatch: pytest.MonkeyPatch): json=wiring_diagram.model_dump(), ) + @pytest.mark.usefixtures("test_mc_build") def test_docker_compose(base_path: Path, monkeypatch: pytest.MonkeyPatch): build_path = base_path / "build" / TEST_SIMULATION @@ -131,9 +134,9 @@ def test_docker_compose(base_path: Path, monkeypatch: pytest.MonkeyPatch): stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) as proc: - assert proc.returncode != 0, ( - f"docker-compose failed with {proc.returncode}: {proc.stderr.read()}" - ) + assert ( + proc.returncode != 0 + ), f"docker-compose failed with {proc.returncode}: {proc.stderr.read()}" stdout = b"" start = time.time() fail = True From 1a3acd279c527e75b034d8fd4912405816513f57 Mon Sep 17 00:00:00 2001 From: Joseph McKinsey Date: Wed, 21 Jan 2026 11:15:47 -0700 Subject: [PATCH 05/13] Start switching to ty instead of mypy --- .pre-commit-config.yaml | 13 ++++--------- pyproject.toml | 18 ++++++++++++++---- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 433da05..1d7fe72 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,12 +22,7 @@ repos: - id: check-toml - id: mixed-line-ending - # Type checking with mypy - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.14.1 - hooks: - - id: mypy - additional_dependencies: - - pydantic~=2.0 - - types-PyYAML - args: [--ignore-missing-imports] + # Type checking with ty (Astral's fast type checker) + # TODO: Add ty pre-commit hook once official support is available + # See: https://github.com/astral-sh/ty/issues/269 + # For now, run manually with: uv run ty check src/ diff --git a/pyproject.toml b/pyproject.toml index e4a41c6..8eb7a63 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,6 @@ text = "BSD 3-Clause" [project.optional-dependencies] test = [ - "black", "bump-my-version", "mypy~=1.0", "pre-commit", @@ -47,6 +46,7 @@ test = [ "numpy", "pandas", "pyarrow", + "ty>=0.0.13", ] metrics = ["pandas", "numpy", "pyarrow"] @@ -69,9 +69,6 @@ readme = { file = ["README.md"], content-type = "text/markdown" } "*" = ["*.json", "*.csv", "*.sh"] # Linter + formatter configuration -[tool.black] -line-length = 90 - [tool.ruff] line-length = 92 select = [ @@ -96,3 +93,16 @@ target-version = "py310" [tool.ruff.pydocstyle] convention = "numpy" + +# Mypy configuration +[tool.mypy] +exclude = [ + "^tests/unit_tests/test_oedisi_tools/broker/.*", + "^tests/unit_tests/test_oedisi_tools/component1/.*", + "^tests/unit_tests/test_oedisi_tools/component2/.*", + "^tests/unit_tests/test_oedisi_tools/build/.*", +] + +# ty configuration (Astral's fast type checker) +[tool.ty.rules] +unresolved-import = "ignore" From 051062dfb1ac2f0b3c5f326cc6baaefa7c13dd08 Mon Sep 17 00:00:00 2001 From: Joseph McKinsey Date: Wed, 21 Jan 2026 11:28:45 -0700 Subject: [PATCH 06/13] Fix problems pointed out by ty (skipping a ruff check) --- .../componentframework/mock_component.py | 6 ++-- .../system_configuration.py | 25 +++++++++++++---- src/oedisi/tools/testing_broker.py | 3 +- src/oedisi/types/data_types.py | 28 ++++++++++++++----- 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/oedisi/componentframework/mock_component.py b/src/oedisi/componentframework/mock_component.py index 24fe0c2..6347b5d 100644 --- a/src/oedisi/componentframework/mock_component.py +++ b/src/oedisi/componentframework/mock_component.py @@ -28,9 +28,9 @@ def __init__( name, parameters: dict[str, dict[str, str]], directory: str, - host: str = None, - port: int = None, - comp_type: str = None, + host: str | None = None, + port: int | None = None, + comp_type: str | None = None, ): self._name = name self._directory = directory diff --git a/src/oedisi/componentframework/system_configuration.py b/src/oedisi/componentframework/system_configuration.py index c230d9b..4cb5061 100644 --- a/src/oedisi/componentframework/system_configuration.py +++ b/src/oedisi/componentframework/system_configuration.py @@ -19,7 +19,7 @@ import logging import shutil import psutil -from abc import ABC, abstractmethod, abstractproperty +from abc import ABC, abstractmethod from pydantic import field_validator, BaseModel, ValidationInfo from oedisi.types.common import DOCKER_HUB_USER, APP_NAME @@ -67,19 +67,34 @@ class ComponentType(ABC): to run the component. """ + @abstractmethod + def __init__( + self, + name: str, + parameters: dict[str, dict[str, str]], + directory: str, + host: str | None = None, + port: int | None = None, + comp_type: str | None = None, + ): + pass + @abstractmethod def generate_input_mapping(self, links: dict[str, str]): pass - @abstractproperty + @property + @abstractmethod def execute_function(self): pass - @abstractproperty + @property + @abstractmethod def dynamic_inputs(self): pass - @abstractproperty + @property + @abstractmethod def dynamic_outputs(self): pass @@ -224,7 +239,7 @@ def initialize_federates( component.parameters, directory, component.host, - component.port, + component.container_port, component.type, ) components[component.name] = initialized_component diff --git a/src/oedisi/tools/testing_broker.py b/src/oedisi/tools/testing_broker.py index acac949..6d78135 100644 --- a/src/oedisi/tools/testing_broker.py +++ b/src/oedisi/tools/testing_broker.py @@ -1,4 +1,5 @@ import time +from typing import cast import helics as h @@ -52,7 +53,7 @@ def wait_until_connected(self): print("Waiting for initialization") while True: time.sleep(2) - current_state = self.broker.query("broker", "current_state") + current_state = cast(dict, self.broker.query("broker", "current_state")) cores = current_state["cores"] if len(cores) == 2 and all(core["state"] == "init_requested" for core in cores): return diff --git a/src/oedisi/types/data_types.py b/src/oedisi/types/data_types.py index c5cea33..dde9feb 100644 --- a/src/oedisi/types/data_types.py +++ b/src/oedisi/types/data_types.py @@ -1,7 +1,7 @@ from __future__ import annotations import datetime from enum import Enum -from pydantic import model_validator, BaseModel, RootModel +from pydantic import model_validator, BaseModel, RootModel, Field ### Supporting Functions ### # TODO: Connect with CIM values @@ -251,12 +251,26 @@ class AdmittanceMatrix(BaseModel): class Injection(BaseModel): # Shouldn't these be equipment arrays? - current_real: CurrentsReal = {"values": [], "ids": [], "node_ids": []} - current_imaginary: CurrentsImaginary = {"values": [], "ids": [], "node_ids": []} - power_real: PowersReal = {"values": [], "ids": [], "node_ids": []} - power_imaginary: PowersImaginary = {"values": [], "ids": [], "node_ids": []} - impedance_real: ImpedanceReal = {"values": [], "ids": [], "node_ids": []} - impedance_imaginary: ImpedanceImaginary = {"values": [], "ids": [], "node_ids": []} + current_real: CurrentsReal = Field( + default_factory=lambda: CurrentsReal(values=[], ids=[], units="A") + ) + current_imaginary: CurrentsImaginary = Field( + default_factory=lambda: CurrentsImaginary(values=[], ids=[], units="A") + ) + power_real: PowersReal = Field( + default_factory=lambda: PowersReal(values=[], ids=[], equipment_ids=[], units="kW") + ) + power_imaginary: PowersImaginary = Field( + default_factory=lambda: PowersImaginary( + values=[], ids=[], equipment_ids=[], units="kVAR" + ) + ) + impedance_real: ImpedanceReal = Field( + default_factory=lambda: ImpedanceReal(values=[], ids=[], units="Ohm") + ) + impedance_imaginary: ImpedanceImaginary = Field( + default_factory=lambda: ImpedanceImaginary(values=[], ids=[], units="Ohm") + ) class Command(BaseModel): From 08369e1120669205c4b8f9256929d4ee048f0986 Mon Sep 17 00:00:00 2001 From: Joseph McKinsey Date: Wed, 21 Jan 2026 11:32:54 -0700 Subject: [PATCH 07/13] Update the ruff config to use lint. --- pyproject.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8eb7a63..3f7a472 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,7 +71,7 @@ readme = { file = ["README.md"], content-type = "text/markdown" } # Linter + formatter configuration [tool.ruff] line-length = 92 -select = [ +lint.select = [ "E", # pycodestyle "TD", # flake-8 todos "PD", # pandas vet @@ -88,10 +88,10 @@ select = [ target-version = "py310" # 4. Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`. -[tool.ruff.per-file-ignores] -#"__init__.py" = ["E402", "F401", "D104"] +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["E402", "F401", "D104"] -[tool.ruff.pydocstyle] +[tool.ruff.lint.pydocstyle] convention = "numpy" # Mypy configuration From d9b7fbf3b735e0ffaa02043bcdbd8c12c60669cf Mon Sep 17 00:00:00 2001 From: Joseph McKinsey Date: Wed, 21 Jan 2026 11:48:01 -0700 Subject: [PATCH 08/13] Add a few ruff exceptions --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 3f7a472..5c2f335 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,6 +90,8 @@ target-version = "py310" # 4. Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`. [tool.ruff.lint.per-file-ignores] "__init__.py" = ["E402", "F401", "D104"] +"src/oedisi/tools/__init__.py" = ["F403", "F405"] +"src/oedisi/types/generate_schema.py" = ["F403", "F405"] [tool.ruff.lint.pydocstyle] convention = "numpy" From a446e86f7b11d85f2811d2698a403e0f605f6e02 Mon Sep 17 00:00:00 2001 From: Joseph McKinsey Date: Wed, 21 Jan 2026 11:48:20 -0700 Subject: [PATCH 09/13] Fix non-docstring errors --- .../system_configuration.py | 26 +++++++++--------- .../wiring_diagram_utils.py | 16 ++++++----- src/oedisi/tools/cli_tools.py | 27 +++++++++++-------- 3 files changed, 40 insertions(+), 29 deletions(-) diff --git a/src/oedisi/componentframework/system_configuration.py b/src/oedisi/componentframework/system_configuration.py index 4cb5061..4891b11 100644 --- a/src/oedisi/componentframework/system_configuration.py +++ b/src/oedisi/componentframework/system_configuration.py @@ -196,8 +196,8 @@ def check_link_names(cls, links, info: ValidationInfo): def add_component(self, c: Component): self.components.append(c) - def add_link(self, l: Link): - self.links.append(l) + def add_link(self, link: Link): + self.links.append(link) @classmethod def empty(cls, name="unnamed"): @@ -244,15 +244,17 @@ def initialize_federates( ) components[component.name] = initialized_component - for l in wiring_diagram.links: - source_types = components[l.source].dynamic_outputs - target_types = components[l.target].dynamic_inputs - assert l.source_port in source_types, f"{l.source} does not have {l.source_port}" + for link in wiring_diagram.links: + source_types = components[link.source].dynamic_outputs + target_types = components[link.target].dynamic_inputs + assert link.source_port in source_types, ( + f"{link.source} does not have {link.source_port}" + ) assert ( - l.target_port in target_types - ), f"{l.target} does not have dynamic input {l.target_port}" - source_type = source_types[l.source_port] - target_type = target_types[l.target_port] + link.target_port in target_types + ), f"{link.target} does not have dynamic input {link.target_port}" + source_type = source_types[link.source_port] + target_type = target_types[link.target_port] assert compatability_checker( source_type, target_type ), f"{source_type} is not compatible with {target_type}" @@ -261,7 +263,7 @@ def initialize_federates( for name, component in components.items(): links = link_map[name] component.generate_input_mapping( - {l.target_port: f"{l.source}/{l.source_port}" for l in links} + {link.target_port: f"{link.source}/{link.source_port}" for link in links} ) federates.append( @@ -335,4 +337,4 @@ def generate_runner_config( name="broker", exec=f"helics_broker -f {len(federates)} --loglevel=warning", ) - return RunnerConfig(name=wiring_diagram.name, federates=(federates + [broker_federate])) + return RunnerConfig(name=wiring_diagram.name, federates=([*federates, broker_federate])) diff --git a/src/oedisi/componentframework/wiring_diagram_utils.py b/src/oedisi/componentframework/wiring_diagram_utils.py index ad8239d..7c82d48 100644 --- a/src/oedisi/componentframework/wiring_diagram_utils.py +++ b/src/oedisi/componentframework/wiring_diagram_utils.py @@ -15,8 +15,12 @@ def get_graph(wiring_diagram: WiringDiagram): g = nx.MultiDiGraph() for c in wiring_diagram.components: g.add_node(c.name, type=c.type, parameters=c.parameters) - for l in wiring_diagram.links: - g.add_edge(l.source, l.target, source_port=l.source_port, target_port=l.target_port) + for link in wiring_diagram.links: + g.add_edge( + link.source, link.target, + source_port=link.source_port, + target_port=link.target_port + ) return g @@ -38,8 +42,8 @@ def plot_graph_matplotlib(wiring_diagram: WiringDiagram): ) edge_map = { - (l.source, l.target): f"{l.source_port} -> {l.target_port}" - for l in wiring_diagram.links + (link.source, link.target): f"{link.source_port} -> {link.target_port}" + for link in wiring_diagram.links } nx.draw_networkx_edge_labels(g, pos, edge_map, font_color="red") @@ -47,7 +51,7 @@ def plot_graph_matplotlib(wiring_diagram: WiringDiagram): plt.show() -def get_graph_renderer(G): +def get_graph_renderer(G): # noqa: N803 import networkx as nx from bokeh.plotting import from_networkx from bokeh.models import Circle, EdgesOnly, MultiLine @@ -87,7 +91,7 @@ def plot_graph_bokeh(wiring_diagram: WiringDiagram): from bokeh.layouts import layout from bokeh.io import show - G = get_graph(wiring_diagram) + G = get_graph(wiring_diagram) # noqa: N806 graph_renderer = get_graph_renderer(G) source = graph_renderer.node_renderer.data_source diff --git a/src/oedisi/tools/cli_tools.py b/src/oedisi/tools/cli_tools.py index 944ce74..1ef4409 100644 --- a/src/oedisi/tools/cli_tools.py +++ b/src/oedisi/tools/cli_tools.py @@ -80,7 +80,8 @@ def get_basic_component(filename): is_flag=True, default=False, show_default=True, - help="Use the flag to create docker-compose config files for a multi-container implementation.", + help="Use the flag to create docker-compose config files for a " + "multi-container implementation.", ) @click.option( "-p", "--broker-port", default=8766, show_default=True, help="Pass the broker port." @@ -159,12 +160,14 @@ def build( def validate_optional_inputs(wiring_diagram: WiringDiagram, component_dict_of_files: dict): for component in wiring_diagram.components: - assert hasattr( - component, "host" - ), f"host parameter required for component {component.name} for multi-continer model build" - assert hasattr( - component, "container_port" - ), f"post parameter required for component {component.name} for multi-continer model build" + assert hasattr(component, "host"), ( + f"host parameter required for component {component.name} " + "for multi-continer model build" + ) + assert hasattr(component, "container_port"), ( + f"post parameter required for component {component.name} " + "for multi-continer model build" + ) def drop_null_values(model: Any) -> dict: @@ -274,9 +277,10 @@ def create_single_kubernetes_deyployment( def edit_docker_file(file_path, component: Component): dir_path = os.path.abspath(os.path.join(file_path, os.pardir)) server_file = os.path.join(dir_path, "server.py") - assert os.path.exists( - server_file - ), f"Server.py file missing for {component.name}.REST API implementation expected in a server.py file" + assert os.path.exists(server_file), ( + f"Server.py file missing for {component.name}." + "REST API implementation expected in a server.py file" + ) with open(file_path, "w") as f: f.write(f"FROM {BASE_DOCKER_IMAGE}\n") @@ -473,7 +477,8 @@ def test_description(target_directory, component_desc, parameters): Examples:: - oedisi test-description --component-desc component/component_definition.json --parameters inputs.json + oedisi test-description --component-desc component/component_definition.json \\ + --parameters inputs.json Initialized broker Waiting for initialization From 00d9c562f9fc7bf911dbc0032348c48f5853209c Mon Sep 17 00:00:00 2001 From: Joseph McKinsey Date: Wed, 21 Jan 2026 11:52:49 -0700 Subject: [PATCH 10/13] Remove unused variable --- tests/unit_tests/test_oedisi_tools/test_multi_container.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests/test_oedisi_tools/test_multi_container.py b/tests/unit_tests/test_oedisi_tools/test_multi_container.py index 16420fc..a9ef7f9 100644 --- a/tests/unit_tests/test_oedisi_tools/test_multi_container.py +++ b/tests/unit_tests/test_oedisi_tools/test_multi_container.py @@ -117,7 +117,7 @@ def test_api_run(base_path: Path, monkeypatch: pytest.MonkeyPatch): # Connection error is raised as the broker running, but not all components are up yet. # wheb broker make s post request to components, they are not available yet. with pytest.raises(ConnectionError): - response = client.post( + client.post( "/configure/", json=wiring_diagram.model_dump(), ) From d17b8b11e00711ff49f53a816341565beecd347a Mon Sep 17 00:00:00 2001 From: Joseph McKinsey Date: Wed, 21 Jan 2026 12:02:15 -0700 Subject: [PATCH 11/13] Fix test ruff checks (that aren't docstrings) --- .../test_oedisi_tools/broker/server.py | 31 +++++++++++-------- .../test_oedisi_tools/test_multi_container.py | 17 +++++++--- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/tests/unit_tests/test_oedisi_tools/broker/server.py b/tests/unit_tests/test_oedisi_tools/broker/server.py index 822236d..e366826 100644 --- a/tests/unit_tests/test_oedisi_tools/broker/server.py +++ b/tests/unit_tests/test_oedisi_tools/broker/server.py @@ -51,7 +51,8 @@ def read_settings(): else: raise HTTPException( status_code=404, - detail="Use the '/configure' setpoint to setup up the WiringDiagram before making requests other enpoints", + detail="Use the '/configure' setpoint to setup up the WiringDiagram before " + "making requests other enpoints", ) return component_map, broker_ip, api_port @@ -79,7 +80,10 @@ def build_url(host: str, port: int, enpoint: list): async def run_simulation(): component_map, broker_ip, api_port = read_settings() logger.info(f"{broker_ip}, {api_port}") - initstring = f"-f {len(component_map)-1} --name=mainbroker --loglevel=trace --local_interface={broker_ip} --localport=23404" + initstring = ( + f"-f {len(component_map) - 1} --name=mainbroker --loglevel=trace" + "--local_interface={broker_ip} --localport=23404" + ) logger.info(f"Broker initaialization string: {initstring}") broker = h.helicsCreateBroker("zmq", "", initstring) @@ -131,21 +135,22 @@ async def run_simulation(): if pending: await asyncio.sleep(5) else: - # ensure exceptions are observed to avoid warnings - for idx, t in enumerate(tasks): - try: - res = t.result() - logger.info( - f"Task {idx} succeeded: {getattr(res, 'status_code', 'N/A')}" - ) - except Exception as exc: - logger.error(f"Task {idx} failed: {exc}") + break + # ensure exceptions are observed to avoid warnings + for idx, t in enumerate(tasks): + try: + res = t.result() + logger.info( + f"Task {idx} succeeded: {getattr(res, 'status_code', 'N/A')}" + ) + except Exception as exc: + logger.error(f"Task {idx} failed: {exc}") while h.helicsBrokerIsConnected(broker): time.sleep(1) query_result = broker.query("broker", "current_state") - logger.info(f"Federates expected: {len(component_map)-1}") - logger.info(f"Federates connected: {len(broker.query("broker", "federates"))}") + logger.info(f"Federates expected: {len(component_map) - 1}") + logger.info(f"Federates connected: {len(broker.query('broker', 'federates'))}") logger.info(f"Simulation state: {query_result['state']}") logger.info(f"Global time: {query_result['attributes']['parent']}") h.helicsCloseLibrary() diff --git a/tests/unit_tests/test_oedisi_tools/test_multi_container.py b/tests/unit_tests/test_oedisi_tools/test_multi_container.py index a9ef7f9..94722be 100644 --- a/tests/unit_tests/test_oedisi_tools/test_multi_container.py +++ b/tests/unit_tests/test_oedisi_tools/test_multi_container.py @@ -41,10 +41,16 @@ def test_mc_build(base_path: Path, monkeypatch: pytest.MonkeyPatch): ), "Broker federate should be implemented before building a multicontainer problem." api_implementation = broker_path / API_FILE - assert api_implementation.exists(), f"A valid REST API implementatiion should exist in {api_implementation} before building a multicontainer problem." + assert api_implementation.exists(), ( + f"A valid REST API implementatiion should exist in {api_implementation} " + "before building a multicontainer problem." + ) requirements_file = broker_path / "requirements.txt" - assert requirements_file.exists(), "All components should have a valid requirements.txt file listing required python packages for the build." + assert requirements_file.exists(), ( + "All components should have a valid requirements.txt file listing required " + "python packages for the build." + ) result = runner.invoke(cli, ["build", "-m", "-i", TEST_SIMULATION]) assert result.exit_code == 0 @@ -110,9 +116,10 @@ def test_api_run(base_path: Path, monkeypatch: pytest.MonkeyPatch): client = TestClient(app) clients[folder.name] = client - assert ( - BROKER_SERVICE in clients - ), f"No broker client in list of tested services. Available services {list(clients.keys())}" + assert BROKER_SERVICE in clients, ( + "No broker client in list of tested services. " + f"Available services {list(clients.keys())}" + ) client = clients[BROKER_SERVICE] # Connection error is raised as the broker running, but not all components are up yet. # wheb broker make s post request to components, they are not available yet. From e75e2cef35b84b4433d60df998d6b3429eccbcda Mon Sep 17 00:00:00 2001 From: Joseph McKinsey Date: Wed, 21 Jan 2026 12:04:07 -0700 Subject: [PATCH 12/13] Run unsafe fixes on docstrings --- src/oedisi/componentframework/basic_component.py | 8 +++----- .../componentframework/system_configuration.py | 16 ++++++++-------- .../componentframework/wiring_diagram_utils.py | 9 +++++---- src/oedisi/tools/broker_utils.py | 4 ++-- src/oedisi/tools/cli_tools.py | 16 ++++++++-------- src/oedisi/tools/metrics.py | 2 +- src/oedisi/types/data_types.py | 10 +++++----- tests/unit_tests/test_oedisi_tools/test_cli.py | 2 +- .../unit_tests/test_oedisi_tools/test_metrics.py | 2 +- .../test_oedisi_tools/test_multi_container.py | 2 +- 10 files changed, 35 insertions(+), 36 deletions(-) diff --git a/src/oedisi/componentframework/basic_component.py b/src/oedisi/componentframework/basic_component.py index 6caece7..8780ab1 100644 --- a/src/oedisi/componentframework/basic_component.py +++ b/src/oedisi/componentframework/basic_component.py @@ -1,6 +1,4 @@ -""" -Generate basic component from description JSON -""" +"""Generate basic component from description JSON.""" import json import os @@ -12,7 +10,7 @@ class ComponentDescription(BaseModel): - """Component description for simple ComponentType + """Component description for simple ComponentType. Parameters ---------- @@ -46,7 +44,7 @@ def component_from_json(filepath, type_checker): def basic_component(comp_desc: ComponentDescription, type_checker): - """Uses data in component_definition to create a new component type + """Uses data in component_definition to create a new component type. Parameters ---------- diff --git a/src/oedisi/componentframework/system_configuration.py b/src/oedisi/componentframework/system_configuration.py index 4891b11..7c7ebe2 100644 --- a/src/oedisi/componentframework/system_configuration.py +++ b/src/oedisi/componentframework/system_configuration.py @@ -147,7 +147,7 @@ class ComponentStruct(BaseModel): class WiringDiagram(BaseModel): - """Cosimulation configuration. This may end up wrapped in another interface""" + """Cosimulation configuration. This may end up wrapped in another interface.""" name: str components: list[Component] @@ -178,7 +178,7 @@ def clean_model(self, target_directory="."): @field_validator("components") @classmethod def check_component_names(cls, components): - """Check that the components all have unique names""" + """Check that the components all have unique names.""" names = set(map(lambda c: c.name, components)) assert len(names) == len(components) return components @@ -205,7 +205,7 @@ def empty(cls, name="unnamed"): class Federate(BaseModel): - """Federate configuration for HELICS CLI""" + """Federate configuration for HELICS CLI.""" directory: str hostname: str = "localhost" @@ -226,7 +226,7 @@ def initialize_federates( compatability_checker, target_directory=".", ) -> list[Federate]: - """Initialize all the federates""" + """Initialize all the federates.""" components = {} link_map = get_link_map(wiring_diagram) for component in wiring_diagram.components: @@ -247,9 +247,9 @@ def initialize_federates( for link in wiring_diagram.links: source_types = components[link.source].dynamic_outputs target_types = components[link.target].dynamic_inputs - assert link.source_port in source_types, ( - f"{link.source} does not have {link.source_port}" - ) + assert ( + link.source_port in source_types + ), f"{link.source} does not have {link.source_port}" assert ( link.target_port in target_types ), f"{link.target} does not have dynamic input {link.target_port}" @@ -281,7 +281,7 @@ def get_link_map(wiring_diagram: WiringDiagram): class RunnerConfig(BaseModel): - """HELICS running config for the full simulation + """HELICS running config for the full simulation. Examples -------- diff --git a/src/oedisi/componentframework/wiring_diagram_utils.py b/src/oedisi/componentframework/wiring_diagram_utils.py index 7c82d48..83ca8cc 100644 --- a/src/oedisi/componentframework/wiring_diagram_utils.py +++ b/src/oedisi/componentframework/wiring_diagram_utils.py @@ -1,4 +1,4 @@ -"""Wiring Diagram utilities +"""Wiring Diagram utilities. Wiring diagrams can be hard to manage in their final list based form. Some utilities plot, and future additions include nested wiring @@ -9,7 +9,7 @@ def get_graph(wiring_diagram: WiringDiagram): - """Get networkx graph representation of wiring_diagram""" + """Get networkx graph representation of wiring_diagram.""" import networkx as nx g = nx.MultiDiGraph() @@ -17,9 +17,10 @@ def get_graph(wiring_diagram: WiringDiagram): g.add_node(c.name, type=c.type, parameters=c.parameters) for link in wiring_diagram.links: g.add_edge( - link.source, link.target, + link.source, + link.target, source_port=link.source_port, - target_port=link.target_port + target_port=link.target_port, ) return g diff --git a/src/oedisi/tools/broker_utils.py b/src/oedisi/tools/broker_utils.py index bdbe2c4..10aa68d 100644 --- a/src/oedisi/tools/broker_utils.py +++ b/src/oedisi/tools/broker_utils.py @@ -2,7 +2,7 @@ class TimeData(BaseModel): - """Time data for a federate""" + """Time data for a federate.""" name: str granted_time: float @@ -10,7 +10,7 @@ class TimeData(BaseModel): def pprint_time_data(time_data): - """A table would be better somehow, but which should be the columns""" + """A table would be better somehow, but which should be the columns.""" print( f""" Name : {time_data.name} diff --git a/src/oedisi/tools/cli_tools.py b/src/oedisi/tools/cli_tools.py index 1ef4409..5b6bb85 100644 --- a/src/oedisi/tools/cli_tools.py +++ b/src/oedisi/tools/cli_tools.py @@ -42,7 +42,7 @@ def cli(): def bad_type_checker(type, x): - """Does not check types""" + """Does not check types.""" return True @@ -94,7 +94,7 @@ def get_basic_component(filename): def build( target_directory, system, component_dict, multi_container, broker_port, simulation_id ): - """Build to the simulation folder + r"""Build to the simulation folder. Examples:: @@ -369,7 +369,7 @@ def create_docker_compose_file( help="Location of helics run json. Usually build/system_runner.json", ) def run(runner): - """Calls out to helics run command + """Calls out to helics run command. Examples:: @@ -473,7 +473,7 @@ def run_mc(runner, kubernetes, docker_compose): help="Path to parameters JSON (default is parameters={})", ) def test_description(target_directory, component_desc, parameters): - """Test component intialization from component description + r"""Test component intialization from component description. Examples:: @@ -578,7 +578,7 @@ def test_description(target_directory, component_desc, parameters): def remove_from_runner_config(runner_config, element): - """Remove federate from configuration""" + """Remove federate from configuration.""" within_feds = [fed for fed in runner_config.federates if fed.name != element] without_feds = [fed for fed in runner_config.federates if fed.name == element] new_config = RunnerConfig(name=runner_config.name, federates=within_feds) @@ -586,7 +586,7 @@ def remove_from_runner_config(runner_config, element): def remove_from_json(system_json, element): - """Remove federate from configuration and resave with revised.json""" + """Remove federate from configuration and resave with revised.json.""" with open(system_json) as f: runner_config = RunnerConfig.model_validate(json.load(f)) new_config, without_feds = remove_from_runner_config(runner_config, element) @@ -607,8 +607,8 @@ def remove_from_json(system_json, element): ) @click.option("--foreground", type=str, help="Name of component to run in background") def debug_component(runner, foreground): - """ - Run system runner json with one component in the JSON + r""" + Run system runner json with one component in the JSON. We remove one component from system_runner.json and then call helics run in the background with our new json. diff --git a/src/oedisi/tools/metrics.py b/src/oedisi/tools/metrics.py index a9a99c3..b8b83ba 100644 --- a/src/oedisi/tools/metrics.py +++ b/src/oedisi/tools/metrics.py @@ -40,7 +40,7 @@ help="Unit of estimated voltages", ) def evaluate_estimate(path, metric, angle_unit): - """Evaluate the estimate of the algorithm against the measurements. + r"""Evaluate the estimate of the algorithm against the measurements. The measurements are assumed to be in the form of .feather files. diff --git a/src/oedisi/types/data_types.py b/src/oedisi/types/data_types.py index dde9feb..cc5796e 100644 --- a/src/oedisi/types/data_types.py +++ b/src/oedisi/types/data_types.py @@ -15,7 +15,7 @@ class StateArray(BaseModel): Extended by classes: "SwitchStates", "CapacitorStates", - "RegulatorStates" + "RegulatorStates". """ @@ -43,7 +43,7 @@ class CostArray(BaseModel): "ReactiveCostFunctions", "RealWholesalePrices", "ReactiveWholesalePrices", - "OperationalCosts" + "OperationalCosts". """ @@ -78,7 +78,7 @@ class MeasurementArray(BaseModel): Extended by classes: "BusArray", "EquipmentArray", - "EquipmentNodeArray" + "EquipmentNodeArray". """ values: list[float] @@ -95,7 +95,7 @@ class BusArray(MeasurementArray): "VoltagesMagnitude", "VoltagesAngle", "VoltagesReal", - "VoltagesImaginary" + "VoltagesImaginary". """ pass @@ -115,7 +115,7 @@ class EquipmentArray(MeasurementArray): "ImpedanceMagnitude", "ImpedanceAngle", "ImpedanceReal", - "ImpedanceImaginary", + "ImpedanceImaginary",. """ pass diff --git a/tests/unit_tests/test_oedisi_tools/test_cli.py b/tests/unit_tests/test_oedisi_tools/test_cli.py index 5e1ff02..c3aa188 100644 --- a/tests/unit_tests/test_oedisi_tools/test_cli.py +++ b/tests/unit_tests/test_oedisi_tools/test_cli.py @@ -6,7 +6,7 @@ @pytest.fixture def base_path() -> Path: - """Get the current folder of the test""" + """Get the current folder of the test.""" return Path(__file__).parent diff --git a/tests/unit_tests/test_oedisi_tools/test_metrics.py b/tests/unit_tests/test_oedisi_tools/test_metrics.py index 50ba3ba..a1c33ac 100644 --- a/tests/unit_tests/test_oedisi_tools/test_metrics.py +++ b/tests/unit_tests/test_oedisi_tools/test_metrics.py @@ -7,7 +7,7 @@ @pytest.fixture def base_path() -> Path: - """Get the current folder of the test""" + """Get the current folder of the test.""" return Path(__file__).parent diff --git a/tests/unit_tests/test_oedisi_tools/test_multi_container.py b/tests/unit_tests/test_oedisi_tools/test_multi_container.py index 94722be..4aee978 100644 --- a/tests/unit_tests/test_oedisi_tools/test_multi_container.py +++ b/tests/unit_tests/test_oedisi_tools/test_multi_container.py @@ -26,7 +26,7 @@ @pytest.fixture def base_path() -> Path: - """Get the current folder of the test""" + """Get the current folder of the test.""" return Path(__file__).parent From 768fd10b616a0123d8478a6e28006c72de525abc Mon Sep 17 00:00:00 2001 From: Joseph McKinsey Date: Fri, 30 Jan 2026 15:59:39 -0700 Subject: [PATCH 13/13] Exclude tests from ruff check --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5c2f335..099efa0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,7 +83,7 @@ lint.select = [ ] # Allow unused variables when underscore-prefixed. #dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" -#exclude = ["tests"] +exclude = ["tests"] target-version = "py310"