From 8a719ce43bc6cf5b4c45efb977722825525137e8 Mon Sep 17 00:00:00 2001 From: Max Chesterfield Date: Sat, 14 Mar 2026 00:03:23 +1100 Subject: [PATCH 1/2] Implement ``Identifiable``, make ``IdentifiedObject`` and ``NameType`` subclasses of ``Identifiable`` Signed-off-by: Max Chesterfield a Signed-off-by: Max Chesterfield make ``Name`` a subclass of identifiable Signed-off-by: Max Chesterfield bump zepben.protobuf to 1.4.0b4. and make work Function names, Type hints, docs, and much more are WRONG, but the code works. Signed-off-by: Max Chesterfield GrpcClient kinda done, could definitely refactor some of the child classes into this base to dedupe stuff Signed-off-by: Max Chesterfield BaseService, Differences and Comparator. Difference, ReferenceResolvers Signed-off-by: Max Chesterfield rename `add_to_network_or_none` to `add_to_service_or_none` as network is misleading and wrong. Signed-off-by: Max Chesterfield I think thats all. Signed-off-by: Max Chesterfield missed some stuff, docs on BaseService were all sorts of whack Signed-off-by: Max Chesterfield PR Changes Signed-off-by: Max Chesterfield fix type hinting in ServiceComparatorValidator Signed-off-by: Max Chesterfield sum hints bad, good now Signed-off-by: Max Chesterfield parameterise, and make pretty Signed-off-by: Max Chesterfield name.mrid and stuff Signed-off-by: Max Chesterfield --- changelog.md | 56 +++++- pyproject.toml | 7 +- src/zepben/ewb/__init__.py | 7 +- .../database/sqlite/common/base_cim_reader.py | 34 ++-- .../sqlite/common/base_service_writer.py | 58 +++--- .../cim/iec61970/base/core/identifiable.py | 89 +++++++++ .../iec61970/base/core/identified_object.py | 83 ++------ .../ewb/model/cim/iec61970/base/core/name.py | 26 ++- .../model/cim/iec61970/base/core/name_type.py | 11 +- .../ewb/services/common/base_service.py | 172 ++++++++++------- .../common/base_service_comparator.py | 8 +- src/zepben/ewb/services/common/difference.py | 6 +- .../services/common/reference_resolvers.py | 12 +- .../common/translator/base_proto2cim.py | 40 +++- .../common/translator/service_differences.py | 10 +- .../customer/translator/customer_proto2cim.py | 14 +- .../diagram/translator/diagram_proto2cim.py | 4 +- .../network/translator/network_proto2cim.py | 180 +++++++++--------- src/zepben/ewb/streaming/get/consumer.py | 94 ++++++--- .../ewb/streaming/get/customer_consumer.py | 42 ++-- .../ewb/streaming/get/diagram_consumer.py | 42 ++-- .../ewb/streaming/get/network_consumer.py | 69 ++++--- .../get/query_network_state_client.py | 13 +- src/zepben/ewb/streaming/grpc/grpc.py | 9 +- .../mutations/update_network_state_client.py | 13 +- src/zepben/ewb/util.py | 28 +-- .../base/core/test_identified_object.py | 14 +- test/cim/iec61970/base/core/test_name.py | 15 +- test/cim/iec61970/base/core/test_name_type.py | 24 +-- .../common/service_comparator_validator.py | 63 +++--- test/services/common/test_base_service.py | 6 + .../common/test_base_service_comparator.py | 2 +- .../translator/test_customer_translator.py | 6 +- .../translator/test_diagram_translator.py | 6 +- .../translator/test_network_translator.py | 6 +- test/streaming/get/mock_server.py | 4 +- test/streaming/get/pb_creators.py | 50 ++--- test/streaming/get/test_consumer.py | 6 +- test/streaming/get/test_customer_consumer.py | 62 +++--- test/streaming/get/test_diagram_consumer.py | 50 ++--- test/streaming/get/test_network_consumer.py | 142 +++++++------- 41 files changed, 892 insertions(+), 691 deletions(-) create mode 100644 src/zepben/ewb/model/cim/iec61970/base/core/identifiable.py diff --git a/changelog.md b/changelog.md index 51bfe6e6b..10df7733d 100644 --- a/changelog.md +++ b/changelog.md @@ -14,9 +14,53 @@ * `ProtectionRelayFunction.relay_info` * `ShuntCompensator.shunt_compensator_info` * `Switch.switch_info` +* `NameType` and `Name` now require constructor args to be supplied as kwargs only. +* `GrpcClient` now requires a `stub` to be passed into its constructor as a kw only arg. +* `BaseService` functions `__contains__`, `get`, `add` and `remove` have replaced their `identified_object` parameters with + `identifiable: Identifiable`. +* `BaseService` members and functions that used to reference `IdentifiedObject` now use `Identifiable` instead. +* `MultiObjectResult.objects` is now a `dict[str, Identifiable]`. This will cause problems if you are explicitly expecting an `IdentifiedObject` to be + returned via a consumer client call without any further type narrowing. +* Private function `CimConsumerClient.extract_identified_objects` has been renamed to `extract_identifiables`. +* `ReferenceDifference` members `source` and `target_value` now reference `Identifiable`. +* Renamed the following gRPC messages and attributes to support identifiable objects that don't descend from `IdentifiedObject` (such as `DataSet`): + * In the `cc` protos: + * `CustomerConsumer.getIdentifiedObjects` -> `CustomerConsumer.getIdentifiables` + * `GetIdentifiedObjectsRequest` -> `GetIdentifiablesRequest` + * `GetIdentifiedObjectsResponse` -> `GetIdentifiablesResponse` + * `GetIdentifiablesResponse.identified_objects` -> `GetIdentifiablesResponse.identifiables` + * `CustomerIdentifiedObject` -> `CustomerIdentifiable` + * `CustomerIdentifiable.identified_object` -> `CustomerIdentifiable.identifiable` + * `GetCustomersForContainerResponse.identified_object` -> `GetCustomersForContainerResponse.identifiable` + * In the `dc` protos: + * `DiagramConsumer.getIdentifiedObjects` -> `DiagramConsumer.getIdentifiables` + * `GetIdentifiedObjectsRequest` -> `GetIdentifiablesRequest` + * `GetIdentifiedObjectsResponse` -> `GetIdentifiablesResponse` + * `GetIdentifiablesResponse.identified_objects` -> `GetIdentifiablesResponse.identifiables` + * `DiagramIdentifiedObject` -> `DiagramIdentifiable` + * `DiagramIdentifiable.identified_object` -> `DiagramIdentifiable.identifiable` + * `GetDiagramObjectsResponse.identified_object` -> `GetDiagramObjectsResponse.identifiable` + * In the `nc` protos: + * `NetworkConsumer.getIdentifiedObjects` -> `NetworkConsumer.getIdentifiables` + * `GetIdentifiedObjectsRequest` -> `GetIdentifiablesRequest` + * `GetIdentifiedObjectsResponse` -> `GetIdentifiablesResponse` + * `GetIdentifiablesResponse.identified_objects` -> `GetIdentifiablesResponse.identifiables` + * `NetworkIdentifiedObject` -> `NetworkIdentifiable` + * `NetworkIdentifiable.identified_object` -> `NetworkIdentifiable.identifiable` + * `GetEquipmentForContainersResponse.identified_object` -> `GetEquipmentForContainersResponse.identifiable` + * `GetEquipmentForRestrictionResponse.identified_object` -> `GetEquipmentForRestrictionResponse.identifiable` ### New Features -* None. +* Added `Identifiable` interface which defines `mrid`. +* Anything implementing `Identifiable` can now be added to a `BaseService`. +* `NameType` and `Name` now implement `Identifiable`. Their mRID will be set to `` and `--`. +* Promoted `BaseServiceReader`, `BaseServiceWriter`, `BaseCimReader`, `BaseCollectionReader`, `BaseCollectionWriter`, and `BaseServiceReader` to the public API. +* Added `customerIdentifiable` to replace `customer_identified_object` with support for `Identifiable` object types in the future. +* Added `diagramIdentifiable` to replace `diagram_identified_object` with support for `Identifiable` object types in the future. +* Added `networkIdentifiable` to replace `network_identified_object` with support for `Identifiable` object types in the future. +* Added the following functions to all `CimConsumerClient` descendants: + * `get_identifiable` which replaces the now deprecated `get_identified_object`. + * `get_identifiables` which replaces the now deprecated `get_identified_objects`. ### Enhancements * Added sequence unpacking support for `UnresolvedReference` and `ObjectDifference`. @@ -25,13 +69,21 @@ * You can now pass a list of `TransformerEndRatedS` to the `PowerTransformerEnd` constructor via the `ratings` argument. * Updated all `Callable` type signatures for callables with unused return values to accept `Any` instead of `None`. The return is still unused, but requiring `None` raises types errors if anything is actually returned. +* `IdentifiedObject`, `Name` and `NameType` now extends `Identifiable`. +* `BaseServiceComparator` will now compare all `Identifiable` objects that have been added, not just `IdentifiedObject` objects. ### Fixes * Fixed the packing and unpacking of timestamps for `Agreement.validity_interval` in gRPC messages. Fix also ensures all other timestamps correctly support `None` when optional. +* `BaseService.__contains__`` will now return `false` when passed an `Identifiable` that is not in the service, instead of raising a `KeyError` ### Notes -* None. +* Deprecated the `customer_identified_object` function, please use the replacement `customer_identifiable`. +* Deprecated the `diagram_identified_object` function, please use the replacement `diagram_identifiable`. +* Deprecated the `network_identified_object` function, please use the replacement `network_identifiable`. +* Deprecated the following functions on all `CimConsumerClient` descendants: + * `get_identified_object` which has been replaced with `get_identifiable`. + * `get_identified_objects` which has been replaced with `get_identifiables`. ## [1.2.0] - 2026-03-03 ### Breaking Changes diff --git a/pyproject.toml b/pyproject.toml index 6d8272a63..8e01438c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,11 +23,12 @@ {name = "Max Chesterfield", email = "max.chesterfield@zepben.com"} ] dependencies = [ - "zepben.protobuf==1.3.0", - "typing_extensions==4.14.1", + "zepben.protobuf==1.4.0b4", + "typing_extensions==4.15.0", "requests==2.32.5", "urllib3==2.5.0", - "PyJWT==2.10.1" + "PyJWT==2.10.1", + "typing-extensions==4.15.0", ] classifiers = [ "Programming Language :: Python :: 3", diff --git a/src/zepben/ewb/__init__.py b/src/zepben/ewb/__init__.py index e2979ff9f..325a6cc73 100644 --- a/src/zepben/ewb/__init__.py +++ b/src/zepben/ewb/__init__.py @@ -134,6 +134,7 @@ from zepben.ewb.model.cim.iec61970.base.core.equipment_container import * from zepben.ewb.model.cim.iec61970.base.core.feeder import * from zepben.ewb.model.cim.iec61970.base.core.geographical_region import * +from zepben.ewb.model.cim.iec61970.base.core.identifiable import * from zepben.ewb.model.cim.iec61970.base.core.identified_object import * from zepben.ewb.model.cim.iec61970.base.core.name import * from zepben.ewb.model.cim.iec61970.base.core.name_type import * @@ -579,8 +580,12 @@ from zepben.ewb.database.sqlite.network.network_database_tables import * from zepben.ewb.database.sqlite.extensions.prepared_statement import * from zepben.ewb.database.sqlite.tables.exceptions import * +from zepben.ewb.database.sqlite.common.base_cim_reader import * from zepben.ewb.database.sqlite.common.base_cim_writer import * +from zepben.ewb.database.sqlite.common.base_collection_reader import * +from zepben.ewb.database.sqlite.common.base_collection_writer import * from zepben.ewb.database.sqlite.common.base_entry_writer import * +from zepben.ewb.database.sqlite.common.base_service_reader import * from zepben.ewb.database.sqlite.common.base_service_writer import * from zepben.ewb.database.sqlite.common.metadata_collection_writer import * from zepben.ewb.database.sqlite.common.metadata_entry_writer import * @@ -598,8 +603,6 @@ from zepben.ewb.database.sqlite.network.network_database_writer import * from zepben.ewb.database.sqlite.network.network_service_writer import * from zepben.ewb.database.sqlite.extensions.result_set import ResultSet -from zepben.ewb.database.sqlite.common.base_cim_reader import * -from zepben.ewb.database.sqlite.common.base_service_reader import * from zepben.ewb.database.sqlite.common.metadata_collection_reader import * from zepben.ewb.database.sqlite.common.metadata_entry_reader import * from zepben.ewb.database.sqlite.customer.customer_cim_reader import * diff --git a/src/zepben/ewb/database/sqlite/common/base_cim_reader.py b/src/zepben/ewb/database/sqlite/common/base_cim_reader.py index bcf175c34..5651e5206 100644 --- a/src/zepben/ewb/database/sqlite/common/base_cim_reader.py +++ b/src/zepben/ewb/database/sqlite/common/base_cim_reader.py @@ -3,11 +3,13 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. +from __future__ import annotations + __all__ = ["BaseCimReader"] import logging from abc import ABC -from typing import Callable, Optional, Type +from typing import Callable, Optional, Type, TYPE_CHECKING from zepben.ewb.database.sqlite.common.reader_exceptions import DuplicateMRIDException from zepben.ewb.database.sqlite.extensions.result_set import ResultSet @@ -20,10 +22,14 @@ from zepben.ewb.model.cim.iec61968.common.document import Document from zepben.ewb.model.cim.iec61968.common.organisation import Organisation from zepben.ewb.model.cim.iec61968.common.organisation_role import OrganisationRole -from zepben.ewb.model.cim.iec61970.base.core.identified_object import IdentifiedObject, TIdentifiedObject +from zepben.ewb.model.cim.iec61970.base.core.identifiable import Identifiable +from zepben.ewb.model.cim.iec61970.base.core.identified_object import IdentifiedObject from zepben.ewb.model.cim.iec61970.base.core.name_type import NameType from zepben.ewb.services.common.base_service import BaseService +if TYPE_CHECKING: + from zepben.ewb.model.cim.iec61970.base.core.identifiable import TIdentifiable + class BaseCimReader(ABC): """ @@ -154,28 +160,30 @@ def load_name_types(self, table: TableNameTypes, result_set: ResultSet, set_last :raises SQLException: For any errors encountered reading from the database. """ # noinspection PyArgumentList - name_type = NameType(set_last_name_type(result_set.get_string(table.name_.query_index))) + name_type = NameType(name=set_last_name_type(result_set.get_string(table.name_.query_index))) name_type.description = result_set.get_string(table.description.query_index) return self._add_or_throw_name_type(name_type) - def _add_or_throw(self, identified_object: IdentifiedObject) -> bool: + ############# + # End Model # + ############# + + def _add_or_throw(self, identifiable: Identifiable) -> bool: """ - Try and add the `identified_object` to the `service`, and throw an `Exception` if unsuccessful. + Try and add the `identifiable` to the `service`, and throw an `Exception` if unsuccessful. - :param identified_object: The `IdentifiedObject` to add to the `service`. + :param identifiable: The `identifiable` to add to the `service`. :return: True in all instances, otherwise it throws. - :raises DuplicateMRIDException: If the `IdentifiedObject.mRID` has already been used. - :raises UnsupportedIdentifiedObjectException: If the `IdentifiedObject` is not supported by the `service`. This is an indication of an internal coding - issue, rather than a problem with the data being read, and in a correctly configured system will never occur. + :raises DuplicateMRIDException: If the `identifiable.mRID` has already been used. """ - if self._service.add(identified_object): + if self._service.add(identifiable): return True else: - duplicate = self._service.get(identified_object.mrid) + duplicate = self._service.get(identifiable.mrid) raise DuplicateMRIDException( - f"Failed to load {identified_object}. " + + f"Failed to load {identifiable}. " + f"Unable to add to service '{self._service.name}': duplicate MRID ({duplicate})" ) @@ -196,7 +204,7 @@ def _add_or_throw_name_type(self, name_type: NameType) -> bool: f"Unable to add to service '{self._service.name}': duplicate NameType)" ) - def _ensure_get(self, mrid: Optional[str], type_: Type[TIdentifiedObject] = IdentifiedObject) -> Optional[TIdentifiedObject]: + def _ensure_get(self, mrid: Optional[str], type_: Type[TIdentifiable] = Identifiable) -> Optional[TIdentifiable]: """ Optionally get an object associated with this service and throw if it is not found. diff --git a/src/zepben/ewb/database/sqlite/common/base_service_writer.py b/src/zepben/ewb/database/sqlite/common/base_service_writer.py index d4507d846..c68068d6d 100644 --- a/src/zepben/ewb/database/sqlite/common/base_service_writer.py +++ b/src/zepben/ewb/database/sqlite/common/base_service_writer.py @@ -3,17 +3,20 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. +from __future__ import annotations + __all__ = ["BaseServiceWriter"] from abc import ABC, abstractmethod -from typing import Callable, Type - -from zepben.ewb.database.sqlite.common.base_cim_writer import BaseCimWriter from zepben.ewb.database.sqlite.common.base_collection_writer import BaseCollectionWriter -from zepben.ewb.model.cim.iec61970.base.core.identified_object import TIdentifiedObject -from zepben.ewb.model.cim.iec61970.base.core.name import Name -from zepben.ewb.model.cim.iec61970.base.core.name_type import NameType -from zepben.ewb.services.common.base_service import BaseService +from typing import Callable, Type, TYPE_CHECKING + +if TYPE_CHECKING: + from zepben.ewb.database.sqlite.common.base_cim_writer import BaseCimWriter + from zepben.ewb.services.common.base_service import BaseService + from zepben.ewb.model.cim.iec61970.base.core.identifiable import Identifiable + from zepben.ewb.model.cim.iec61970.base.core.name import Name + from zepben.ewb.model.cim.iec61970.base.core.name_type import NameType class BaseServiceWriter(BaseCollectionWriter, ABC): @@ -47,38 +50,33 @@ def _do_save(self) -> bool: :return: True if the objects were successfully saved to the database, otherwise False """ - """ - Save each object of the specified type using the provided `saver`. - - @param T The type of object to save to the database. - @param saver The callback used to save the objects to the database. Will be called once for each object and should return True if the object is - successfully saved to the database. - - :return: True if all objects are successfully saved to the database, otherwise False. - """ + def _save_each_object(self, type_: Type[Identifiable], saver: Callable[[Identifiable], bool]) -> bool: + """ + Save each object of the specified type using the provided `saver`. + + @param T The type of object to save to the database. + @param saver The callback used to save the objects to the database. Will be called once for each object and should return True if the object is + successfully saved to the database. - def _save_each_object(self, type_: Type[TIdentifiedObject], saver: Callable[[TIdentifiedObject], bool]) -> bool: + :return: True if all objects are successfully saved to the database, otherwise False. + """ status = True for it in self._service.objects(type_): status = status and self._validate_save_object(it, saver) return status - """ - Validate that an object is actually saved to the database, logging an error if anything goes wrong. - - @param T The type of object being saved. - @param it The object being saved. - @param saver The callback actually saving the object to the database. - - :return: True if the object is successfully saved to the database, otherwise False. - """ + def _validate_save_object(self, it: Identifiable, saver: Callable[[Identifiable], bool]) -> bool: + """ + Validate that an object is actually saved to the database, logging an error if anything goes wrong. - def _validate_save_object(self, it: TIdentifiedObject, saver: Callable[[TIdentifiedObject], bool]) -> bool: - def log_error(e: Exception): - self._logger.error(f"Failed to save {it.__class__.__name__} {it.name} [{it.mrid}]: {e}") + @param T The type of object being saved. + @param it The object being saved. + @param saver The callback actually saving the object to the database. - return self._validate_save(it, saver, log_error) + :return: True if the object is successfully saved to the database, otherwise False. + """ + return self._validate_save(it, saver, lambda e: self._logger.error(f"Failed to save {it.__class__.__name__} {it.name} [{it.mrid}]: {e}")) def _save_name_types(self) -> bool: status = True diff --git a/src/zepben/ewb/model/cim/iec61970/base/core/identifiable.py b/src/zepben/ewb/model/cim/iec61970/base/core/identifiable.py new file mode 100644 index 000000000..0cff19e6e --- /dev/null +++ b/src/zepben/ewb/model/cim/iec61970/base/core/identifiable.py @@ -0,0 +1,89 @@ +# Copyright 2026 Zeppelin Bend Pty Ltd +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +__all__ = ["Identifiable", "TIdentifiable"] + +from abc import ABCMeta, abstractmethod +from typing import TypeVar, overload, Callable, Any + +from zepben.ewb import require +from zepben.ewb.dataclassy import dataclass + +TIdentifiable = TypeVar('TIdentifiable') + +T = TypeVar('T') + + +@dataclass(slots=True) +class Identifiable(metaclass=ABCMeta): + """ + An interface that marks an object as identifiable. + + :var mrid: The identifier for this object. + """ + mrid: str + + def __init__(self, **kwargs): + if kwargs: + raise TypeError("unexpected keyword arguments in Identified constructor: {}".format(kwargs)) + + @abstractmethod + def __str__(self) -> str: + """ + Printable version of the object including its name, mrid, and optionally, type + """ + + @overload + def _validate_reference(self, other: 'Identifiable', getter: Callable[[str], 'Identifiable | None'], type_description: str) -> bool: ... + + @overload + def _validate_reference(self, other: T, get_identifier: Callable[[Callable], str], getter: Callable[[str], T | None], type_description: Callable[[], str]) -> bool: ... + + # FIXME: in python 3.11, the IdentifiedObject type hint can be replaced with Self, and this can all be moved into the class def. + # @singledispatchmethod + def _validate_reference(self, other: 'Identifiable | T', getter: Callable[[str], 'Identifiable | T'], type_description: Callable[[], str] | str, get_identifier: Callable[[...], str]=None) -> bool: + """ + Validate whether a given reference exists to `other` using the provided getter function. + + :param other: The object to look up with the getter using its mRID. + :param getter: A function that takes an mRID and returns an `IdentifiedObject`, and throws a `KeyError` if it couldn't be found. + :param type_description: The type description to use for the lazily generated error message. Should be of the form "A[n] type(other)" + :param get_identifier: The function to retrieve the identifier from `other`. + :return: True if `other` was retrieved with `getter` and was equivalent, False otherwise. + :raises ValueError: If the object retrieved from `getter` is not `other`. + """ + if isinstance(other, Identifiable): + get_identifier = lambda _other: _other.mrid + describe_other = lambda: f"{type_description} with mRID {other.mrid}" + else: + require(get_identifier is not None, lambda: "foo") + describe_other = type_description + try: + get_result = getter(get_identifier(other)) + except (KeyError, AttributeError): + return False + + if get_result is other: + return True + + raise ValueError(f"{describe_other()} already exists in {str(self)}") + + def _validate_reference_by_field(self, other: 'Identifiable', field: Any, getter: Callable[[Any], 'Identifiable'], field_name: str) -> bool: + """ + Validate whether a given reference exists to `other` using the provided getter function called with `field`. + + :param other: The object to look up with the getter using its mRID. + :param field: The value of the field from `other` that needs to be validated. + :param getter: A function that takes `field` and returns an `IdentifiedObject`, and throws an `IndexError` if it couldn't be found. + :param field_name: The name of the field to use for the lazily generated error message. + :return: True if `other` was retrieved with `getter` and was equivalent, False otherwise. + :raises ValueError: If the object retrieved from `getter` is not `other`. + """ + try: + get_result = getter(field) + require(get_result is other, lambda: f"Unable to add {other} to {self}. A {get_result} already exists with {field_name} {field}.") + return True + except IndexError: + return False diff --git a/src/zepben/ewb/model/cim/iec61970/base/core/identified_object.py b/src/zepben/ewb/model/cim/iec61970/base/core/identified_object.py index a786cc54f..b97689cf3 100644 --- a/src/zepben/ewb/model/cim/iec61970/base/core/identified_object.py +++ b/src/zepben/ewb/model/cim/iec61970/base/core/identified_object.py @@ -8,23 +8,21 @@ __all__ = ["IdentifiedObject", "TIdentifiedObject"] import logging -from abc import ABCMeta -from typing import Callable, Any, List, Generator, Optional, overload, TypeVar +from typing import List, Generator, Optional, overload, TypeVar -from zepben.ewb.dataclassy import dataclass +from zepben.ewb.model.cim.iec61970.base.core.identifiable import Identifiable from zepben.ewb.model.cim.iec61970.base.core.name import Name from zepben.ewb.model.cim.iec61970.base.core.name_type import NameType from zepben.ewb.util import require, nlen, ngen, safe_remove logger = logging.getLogger(__name__) -TIdentifiedObject = TypeVar("TIdentifiedObject", bound="IdentifiedObject") +TIdentifiedObject = TypeVar("TIdentifiedObject") """ Generic type of IdentifiedObject which can be used for type hinting generics. """ -@dataclass(slots=True) -class IdentifiedObject(metaclass=ABCMeta): +class IdentifiedObject(Identifiable): """ Root class to provide common identification for all classes needing identification and naming attributes. Everything should extend this class, however it's not mandated that every subclass must use all the fields @@ -32,11 +30,10 @@ class IdentifiedObject(metaclass=ABCMeta): All names of attributes of classes extending this class *must* directly reflect CIM properties if they have a direct relation, however must be in snake case to keep the phases PEP compliant. - """ - mrid: str - """Master resource identifier issued by a model authority. The mRID is unique within an exchange context. - Global uniqueness is easily achieved by using a UUID, as specified in RFC 4122, for the mRID. The use of UUID is strongly recommended.""" + :var mrid: Master resource identifier issued by a model authority. The mRID is unique within an exchange context. + Global uniqueness is easily achieved by using a UUID, as specified in RFC 4122, for the mRID. The use of UUID is strongly recommended. + """ name: Optional[str] = None """The name is any free human readable and possibly non unique text naming the object.""" @@ -49,8 +46,6 @@ class IdentifiedObject(metaclass=ABCMeta): # TODO: Missing num_diagram_objects: int = None def has_diagram_objects(self): return (self.num_diagram_objects or 0) > 0 def __init__(self, names: Optional[List[Name]] = None, **kwargs): - if kwargs: - raise TypeError("unexpected keyword arguments in IdentifiedObject constructor: {}".format(kwargs)) super(IdentifiedObject, self).__init__(**kwargs) if not self.mrid or not self.mrid.strip(): raise ValueError("You must provide an mRID for this object.") @@ -166,12 +161,13 @@ def add_name(self, name_type: NameType, name: str) -> IdentifiedObject: name_obj.identified_object = self require(name_obj.identified_object is self, lambda: f"Attempting to add a Name to {str(self)} that does not reference this identified object") - if self.has_name(name_obj.type, name_obj.name): - existing = self.get_name(name_obj.type, name_obj.name) - if existing is name_obj: + try: # If name_obj already exists, ensure it's a duplicate. + if self.get_name(name_obj.type, name_obj.name) is name_obj: return self else: raise ValueError(f"Failed to add duplicate name {str(name)} to {str(self)}.") + except KeyError: + pass self._names = list() if not self._names else self._names self._names.append(name_obj) @@ -204,60 +200,3 @@ def clear_names(self) -> IdentifiedObject: self._names = None return self - - - @overload - def _validate_reference(self, other: IdentifiedObject, getter: Callable[[str], IdentifiedObject | None], type_description: str) -> bool: ... - - @overload - def _validate_reference(self, other: T, get_identifier: Callable[[Callable], str], getter: Callable[[str], T | None], type_description: Callable[[], str]) -> bool: ... - - # FIXME: in python 3.11, the IdentifiedObject type hint can be replaced with Self, and this can all be moved into the class def. - # @singledispatchmethod - def _validate_reference(self, other: IdentifiedObject | T, getter: Callable[[str], IdentifiedObject | T], type_description: Callable[[], str] | str, get_identifier: Callable[[...], str]=None) -> bool: - """ - Validate whether a given reference exists to `other` using the provided getter function. - - :param other: The object to look up with the getter using its mRID. - :param getter: A function that takes an mRID and returns an `IdentifiedObject`, and throws a `KeyError` if it couldn't be found. - :param describe_other: The type description to use for the lazily generated error message. Should be of the form "A[n] type(other)" - :return: True if `other` was retrieved with `getter` and was equivalent, False otherwise. - :raises ValueError: If the object retrieved from `getter` is not `other`. - """ - if isinstance(other, IdentifiedObject): - get_identifier = lambda _other: _other.mrid - describe_other = lambda: f"{type_description} with mRID {other.mrid}" - else: - require(get_identifier is not None, lambda: "foo") - describe_other = type_description - try: - get_result = getter(get_identifier(other)) - except (KeyError, AttributeError): - return False - - if get_result is other: - return True - - raise ValueError(f"{describe_other()} already exists in {str(self)}") - - def _validate_reference_by_field(self, other: IdentifiedObject, field: Any, getter: Callable[[Any], IdentifiedObject], - field_name: str) -> bool: - """ - Validate whether a given reference exists to `other` using the provided getter function called with `field`. - - :param other: The object to look up with the getter using its mRID. - :param field: The value of the field from `other` that needs to be validated. - :param getter: A function that takes `field` and returns an `IdentifiedObject`, and throws an `IndexError` if it couldn't be found. - :param field_name: The name of the field to use for the lazily generated error message. - :return: True if `other` was retrieved with `getter` and was equivalent, False otherwise. - :raises ValueError: If the object retrieved from `getter` is not `other`. - """ - try: - get_result = getter(field) - require(get_result is other, lambda: f"Unable to add {other} to {self}. A {get_result} already exists with {field_name} {field}.") - return True - except IndexError: - return False - - -T = TypeVar('T', ) diff --git a/src/zepben/ewb/model/cim/iec61970/base/core/name.py b/src/zepben/ewb/model/cim/iec61970/base/core/name.py index 95fe712e2..a8983b5e8 100644 --- a/src/zepben/ewb/model/cim/iec61970/base/core/name.py +++ b/src/zepben/ewb/model/cim/iec61970/base/core/name.py @@ -7,25 +7,31 @@ __all__ = ["Name"] -from dataclasses import dataclass from typing import TYPE_CHECKING, Optional +from zepben.ewb.model.cim.iec61970.base.core.identifiable import Identifiable + if TYPE_CHECKING: from zepben.ewb.model.cim.iec61970.base.core.identified_object import IdentifiedObject from zepben.ewb.model.cim.iec61970.base.core.name_type import NameType -# -# NOTE: unsafe_hash is used as we can't mark this class frozen due to needing to update/lazy initialise the identified_object, which shouldn't change once -# it has been initialised. -# -@dataclass(unsafe_hash=True) -class Name: +class Name(Identifiable): """ The Name class provides the means to define any number of human-readable names for an object. A name is **not** to be used for defining inter-object relationships. For inter-object relationships instead use the object identification 'mRID'. """ + @property + def mrid(self) -> str: + return f"{self.name}-{self.type.name}-{self.identified_object.mrid}" + + def __getattribute__(self, item): + # This is a workaround for self.mrid being a property, when we're expecting a string. + if item == "mrid": + return object.__getattribute__(self, 'mrid').fget(self) + return object.__getattribute__(self, item) + name: str """Any free text that name the object.""" @@ -34,3 +40,9 @@ class Name: identified_object: Optional[IdentifiedObject] = None """Identified object that this name designates.""" + + def __str__(self) -> str: + class_name = f'{self.__class__.__name__}' + if self.name: + return f'{class_name}{{{self.mrid}|{self.name}}}' + return f'{class_name}{{{self.mrid}}}' diff --git a/src/zepben/ewb/model/cim/iec61970/base/core/name_type.py b/src/zepben/ewb/model/cim/iec61970/base/core/name_type.py index d09339456..6ec6d27f9 100644 --- a/src/zepben/ewb/model/cim/iec61970/base/core/name_type.py +++ b/src/zepben/ewb/model/cim/iec61970/base/core/name_type.py @@ -9,15 +9,14 @@ from typing import Dict, List, Generator, overload, TYPE_CHECKING, Callable, Optional, Any -from zepben.ewb.dataclassy import dataclass +from zepben.ewb.model.cim.iec61970.base.core.identifiable import Identifiable from zepben.ewb.model.cim.iec61970.base.core.name import Name if TYPE_CHECKING: from zepben.ewb.model.cim.iec61970.base.core.identified_object import IdentifiedObject -@dataclass(slots=True) -class NameType: +class NameType(Identifiable): """ Type of name. Possible values for attribute 'name' are implementation dependent but standard profiles may specify types. An enterprise may have multiple IT systems each having its own local name for the same object, e.g. a planning system may have different names from an EMS. An object may also have @@ -30,6 +29,10 @@ class NameType: name: str """Name of the name type.""" + @property + def mrid(self): + return self.name + description: Optional[str] = None """Description of the name type.""" @@ -193,7 +196,7 @@ def _get_or_create_name(self, name: str, identified_object: IdentifiedObject, up if had_name: name_obj = identified_object.get_name(self, name) else: - name_obj = Name(name, self, identified_object) + name_obj = Name(name=name, type=self, identified_object=identified_object) update_index(name_obj) diff --git a/src/zepben/ewb/services/common/base_service.py b/src/zepben/ewb/services/common/base_service.py index a7b6f7ec1..44bcd1ae0 100644 --- a/src/zepben/ewb/services/common/base_service.py +++ b/src/zepben/ewb/services/common/base_service.py @@ -12,7 +12,7 @@ from typing import Dict, Generator, Callable, Optional, List, Union, Sized, Set, TypeVar, overload from typing import Type -from zepben.ewb.model.cim.iec61970.base.core.identified_object import IdentifiedObject, TIdentifiedObject +from zepben.ewb.model.cim.iec61970.base.core.identifiable import Identifiable, TIdentifiable from zepben.ewb.model.cim.iec61970.base.core.name_type import NameType from zepben.ewb.services.common.meta.metadata_collection import MetadataCollection from zepben.ewb.services.common.reference_resolvers import BoundReferenceResolver, UnresolvedReference @@ -32,14 +32,14 @@ def __init__( self.name: str = name self.metadata: MetadataCollection = metadata or MetadataCollection() - self._objects_by_type: Dict[type, Dict[str, IdentifiedObject]] = OrderedDict() + self._objects_by_type: Dict[type, Dict[str, Identifiable]] = OrderedDict() self._name_types: Dict[str, NameType] = dict() self._unresolved_references_to: Dict[str, Set[UnresolvedReference]] = OrderedDict() """ A dictionary of references between mRID's that as yet have not been resolved - typically when transferring services between systems. The key is the to_mrid of the `UnresolvedReference`s, and the value is a list of `UnresolvedReference`s for that specific object. For example, if an AcLineSegment with mRID 'acls1' is present in the service, but the service is missing its `location` with mRID 'location-l1' - and `perLengthSequenceImpedance` with mRID 'plsi-1', the following key value pairs would be present: + and `per_length_sequence_impedance` with mRID 'plsi-1', the following key value pairs would be present: { "plsi-1": [ @@ -54,8 +54,8 @@ def __init__( ] } - `resolve` in `ReferenceResolver` will be the function used to populate the relationship between the `IdentifiedObject`s either when - `resolveOrDeferReference() is called if the other side of the reference exists in the service, or otherwise when the second object is added to the service. + `resolve` in `ReferenceResolver` will be the function used to populate the relationship between the `Identifiable`s either when + `resolve_or_defer_reference() is called if the other side of the reference exists in the service, or otherwise when the second object is added to the service. """ self._unresolved_references_from: Dict[str, Set[UnresolvedReference]] = OrderedDict() @@ -75,19 +75,31 @@ def __init__( """ @overload - def __contains__(self, mrid: str) -> bool: ... + def __contains__(self, mrid: str) -> bool: + """" + :param mrid: The mRID to search for. + :returns: True if there is an object associated with the specified `mrid`, False otherwise. + """ @overload - def __contains__(self, obj: IdentifiedObject) -> bool: ... + def __contains__(self, obj: Identifiable) -> bool: + """" + :param obj: The ``Identifiable`` to search for. + :returns: True if there is an object in the service that is ``obj``, False otherwise. + """ - def __contains__(self, to_find: str | IdentifiedObject) -> bool: + def __contains__(self, to_find: str | Identifiable) -> bool: """ - Check if `mrid` has any associated object. + Check if ``to_find`` has any associated object. - `mrid` The mRID to search for. - Returns True if there is an object associated with the specified `mrid`, False otherwise. + :param to_find: The mRID or ``Identifiable`` to search for. + :returns: True if there is an object matching ``to_find``, False otherwise. """ - matcher = (lambda it: to_find in it) if isinstance(to_find, str) else (lambda it: it[to_find.mrid] == to_find) + if isinstance(to_find, Identifiable): + matcher = lambda it: it.get(to_find.mrid) == to_find + else: + matcher = lambda it: to_find in it + for type_map in self._objects_by_type.values(): if matcher(type_map): return True @@ -100,16 +112,16 @@ def has_unresolved_references(self, mrid: str = None) -> bool: """ Check if there are `UnresolvedReference`s in the service - `mrid` The mRID to check for `UnresolvedReference`s. If None, will check if any unresolved references exist in the service. - - Returns True if at least one reference exists. + :param mrid: The mRID to check for `UnresolvedReference`s. If None, will check if any unresolved references exist in the service. + :returns: True if at least one reference exists. """ return len(self._unresolved_references_to) > 0 if mrid is None else mrid in self._unresolved_references_to def len_of(self, t: type = None) -> int: """ Get the len of objects of type `t` in the service. - `t` The type of object to get the len of. If None (default), will get the len of all objects in the service. + + :param t: The type of object to get the len of. If None (default), will get the len of all objects in the service. """ if t is None: return sum([len(vals) for vals in self._objects_by_type.values()]) @@ -129,8 +141,9 @@ def len_of(self, t: type = None) -> int: def num_unresolved_references(self, mrid: str = None): """ Get the total number of unresolved references in this service. - `mRID` The mRID to check the number of [UnresolvedReference]s for. If None, will default to number of all unresolved references in the service. - Returns The number of `UnresolvedReference`s. + + :param mrid: The mRID to check the number of [UnresolvedReference]s for. If None, will default to number of all unresolved references in the service. + :returns: The number of `UnresolvedReference`s. """ if mrid is None: return sum([len(r) for r in self._unresolved_references_to.copy().values()]) @@ -148,26 +161,26 @@ def unresolved_references(self) -> Generator[UnresolvedReference, None, None]: for ur in unresolved_refs: yield ur - def get(self, mrid: str, type_: Type[TIdentifiedObject] = IdentifiedObject, default=_GET_DEFAULT, - generate_error: Callable[[str, str], str] = lambda mrid, typ: f"Failed to find {typ}[{mrid}]") -> TIdentifiedObject: + def get(self, mrid: str, type_: Type[TIdentifiable] = Identifiable, default=_GET_DEFAULT, + generate_error: Callable[[str, str], str] = lambda mrid, typ: f"Failed to find {typ}[{mrid}]") -> TIdentifiable: """ Get an object associated with this service. - `mrid` The mRID of the `iec61970.base.core.identified_object.IdentifiedObject` to retrieve. - `type_` The `iec61970.base.core.identified_object.IdentifiedObject` subclass type of the object - with `mrid`. If None, will check all types stored in the service. - `default` The default to return if `mrid` can't be found in the service. - `generate_error` Function to call for an error message. Will be passed the mrid and _type (if set). - Returns The `iec61970.base.core.identified_object.IdentifiedObject` associated with `mrid`, or default + :param mrid: The mRID of the ``iec61970.base.core.identifiable.Identifiable`` to retrieve. + :param type_: The ``iec61970.base.core.identifiable.Identifiable`` subclass type of the object + with ``mrid``. If None, will check all types stored in the service. + :param default: The default to return if ``mrid`` can't be found in the service. + :param generate_error: Function to call for an error message. Will be passed the ``mrid`` and ``type_`` (if set). + :returns: The ``iec61970.base.core.identifiable.Identifiable`` associated with ``mrid``, or ``default`` if it is set. - Raises `KeyError` if `mrid` was not found in the service with `type_`, if no objects of `type_` are - stored by the service and default was not set, or if an empty mRID is provided. + :raises KeyError: if ``mrid`` was not found in the service with ``type_``, if no objects of ``type_`` are + stored by the service and default was not set, or if an empty ``mrid`` is provided. """ if not mrid: raise KeyError("You must specify an mRID to get. Empty/None is invalid.") # This can be written much simpler than below, but we want to avoid throwing any exceptions in this high frequency function - if type_ != IdentifiedObject: + if type_ != Identifiable: objs = self._objects_by_type.get(type_) if objs: obj = objs.get(mrid) @@ -187,30 +200,32 @@ def get(self, mrid: str, type_: Type[TIdentifiedObject] = IdentifiedObject, defa else: return default - def __getitem__(self, mrid): + def __getitem__(self, mrid: str) -> Identifiable: """ Get an object associated with this service. - Note that you should use `get` directly where the type of the desired object is known. - `mrid` The mRID of the `iec61970.base.core.identified_object.IdentifiedObject` to retrieve. - Returns The `iec61970.base.core.identified_object.IdentifiedObject` associated with `mrid`. - Raises `KeyError` if `mrid` was not found in the service with `type`. + Note that you should use ``get`` directly where the type of the desired object is known. + + :param mrid: The mRID of the ``iec61970.base.core.identifiable.Identifiable`` to retrieve. + :returns: The ``iec61970.base.core.identifiable.Identifiable`` associated with ``mrid``. + :raises KeyError: if ``mrid`` was not found in the service with ``type``. """ return self.get(mrid) - def add(self, identified_object: IdentifiedObject) -> bool: + def add(self, identifiable: Identifiable) -> bool: """ Associate an object with this service. - `identified_object` The object to associate with this service. - Returns True if the object is associated with this service, False otherwise. + + :param identifiable: The object to associate with this service. + :returns: True if the object is associated with this service, False otherwise. """ - mrid = identified_object.mrid + mrid = identifiable.mrid if not mrid: return False # TODO: Only allow supported types - objs = self._objects_by_type.get(identified_object.__class__, dict()) + objs = self._objects_by_type.get(identifiable.__class__, dict()) if mrid in objs: - return objs[mrid] is identified_object + return objs[mrid] is identifiable # Check other types and make sure this mRID is unique for obj_map in self._objects_by_type.values(): @@ -220,31 +235,31 @@ def add(self, identified_object: IdentifiedObject) -> bool: unresolved_refs = self._unresolved_references_to.get(mrid, None) if unresolved_refs: for ref in unresolved_refs: - ref.resolver.resolve(ref.from_ref, identified_object) + ref.resolver.resolve(ref.from_ref, identifiable) if ref.reverse_resolver: - ref.reverse_resolver.resolve(identified_object, ref.from_ref) + ref.reverse_resolver.resolve(identifiable, ref.from_ref) self._unresolved_references_from[ref.from_ref.mrid].remove(ref) if not self._unresolved_references_from[ref.from_ref.mrid]: del self._unresolved_references_from[ref.from_ref.mrid] del self._unresolved_references_to[mrid] - objs[mrid] = identified_object - self._objects_by_type[identified_object.__class__] = objs + objs[mrid] = identifiable + self._objects_by_type[identifiable.__class__] = objs return True def resolve_or_defer_reference(self, bound_resolver: BoundReferenceResolver, to_mrid: str) -> bool: """ - Resolves a property reference between two types by looking up the `to_mrid` in the service and - using the provided `bound_resolver` to resolve the reference relationships (including any reverse relationship). + Resolves a property reference between two types by looking up the ``to_mrid`` in the service and + using the provided ``bound_resolver`` to resolve the reference relationships (including any reverse relationship). - If the `to_mrid` object has not yet been added to the service, the reference resolution will be deferred until the - object with `to_mrid` is added to the service, which will then use the resolver from the `bound_resolver` at that + If the ``to_mrid`` object has not yet been added to the service, the reference resolution will be deferred until the + object with ``to_mrid`` is added to the service, which will then use the resolver from the ``bound_resolver`` at that time to resolve the reference relationship. - `bound_resolver` - `to_mrid` The MRID of an object that is the subclass of the to_class of `bound_resolver`. - Returns true if the reference was resolved, otherwise false if it has been deferred. + :param bound_resolver: + :param to_mrid: The MRID of an object that is the subclass of the to_class of ``bound_resolver``. + :returns: true if the reference was resolved, otherwise false if it has been deferred. """ if not to_mrid: return True @@ -284,12 +299,15 @@ def resolve_or_defer_reference(self, bound_resolver: BoundReferenceResolver, to_ self._unresolved_references_from[from_.mrid] = rev_urefs return False - def get_unresolved_reference_mrids_by_resolver(self, - bound_resolvers: Union[BoundReferenceResolver, Sized[BoundReferenceResolver]]) -> Generator[str, None, None]: + def get_unresolved_reference_mrids_by_resolver( + self, + bound_resolvers: Union[BoundReferenceResolver, Sized[BoundReferenceResolver]] + ) -> Generator[str, None, None]: """ - Gets a set of MRIDs that are referenced by the from_obj held by `bound_resolver` that are unresolved. - `bound_resolver` The `BoundReferenceResolver` to retrieve unresolved references for. - Returns Set of mRIDs that have unresolved references. + Gets a set of MRIDs that are referenced by the from_obj held by ``bound_resolvers`` that are unresolved. + + :param bound_resolvers: The ``BoundReferenceResolver`` to retrieve unresolved references for. + :returns: Set of mRIDs that have unresolved references. """ seen = set() try: @@ -308,9 +326,10 @@ def get_unresolved_reference_mrids_by_resolver(self, def get_unresolved_references_from(self, mrid: str) -> Generator[UnresolvedReference, None, None]: """ - Get the `UnresolvedReference`s that `mrid` has to other objects. - `mrid` The mRID to get unresolved references for. - Returns a generator over the `UnresolvedReference`s that need to be resolved for `mrid`. + Get the ``UnresolvedReference``s that ``mrid`` has to other objects. + + :param mrid: The mRID to get unresolved references for. + :returns: a generator over the ``UnresolvedReference``s that need to be resolved for ``mrid``. """ if mrid in self._unresolved_references_from: for ref in self._unresolved_references_from[mrid]: @@ -318,29 +337,31 @@ def get_unresolved_references_from(self, mrid: str) -> Generator[UnresolvedRefer def get_unresolved_references_to(self, mrid: str) -> Generator[UnresolvedReference, None, None]: """ - Get the UnresolvedReferences that other objects have to `mrid`. - `mrid` The mRID to fetch unresolved references for that are pointing to it. - Returns a generator over the `UnresolvedReference`s that need to be resolved for `mrid`. + Get the UnresolvedReferences that other objects have to ``mrid``. + + :param mrid: The mRID to fetch unresolved references for that are pointing to it. + :returns: a generator over the `UnresolvedReference`s that need to be resolved for ``mrid``. """ if mrid in self._unresolved_references_to: for ref in self._unresolved_references_to[mrid]: yield ref - def remove(self, identified_object: IdentifiedObject) -> bool: + def remove(self, identifiable: Identifiable) -> bool: """ Disassociate an object from this service. - `identified_object` THe object to disassociate from the service. - Raises `KeyError` if `identified_object` or its type was not present in the service. + :param identifiable: THe object to disassociate from the service. + :raises KeyError: if `identifiable` or its type was not present in the service. """ - del self._objects_by_type[identified_object.__class__][identified_object.mrid] + del self._objects_by_type[identifiable.__class__][identifiable.mrid] return True - def objects(self, obj_type: Optional[Type[TIdentifiedObject]] = None, exc_types: Optional[List[type]] = None) -> Generator[TIdentifiedObject, None, None]: + def objects(self, obj_type: Optional[Type[TIdentifiable]] = None, exc_types: Optional[List[type]] = None) -> Generator[TIdentifiable, None, None]: """ Generator for the objects in this service of type `obj_type`. - `obj_type` The type of object to yield. If this is a base class it will yield all subclasses. - Returns Generator over + + :param obj_type: The type of object to yield. If this is a base class it will yield all subclasses. + :returns: Generator over ``Identifiable`` objects in this service that are of type ``obj_type``. """ if obj_type is None: for typ, obj_map in self._objects_by_type.items(): @@ -368,9 +389,10 @@ def name_types(self) -> Generator[NameType, None, None]: def add_name_type(self, name_type: NameType) -> bool: """ - Associates the provided `name_type` with this service. - param `name_type` the `NameType` to add to this service - return true if the object is associated with this service, false if an object already exists in the service with + Associates the provided ``name_type`` with this service. + + :param name_type: the ``NameType`` to add to this service + :returns: true if the object is associated with this service, false if an object already exists in the service with the same name. """ if name_type.name in self._name_types: @@ -381,8 +403,10 @@ def add_name_type(self, name_type: NameType) -> bool: def get_name_type(self, name: str) -> NameType: """ - Gets the `NameType` for the provided type name associated with this service. - :raises KeyError: if `name` doesn't exist in this service. + Gets the ``NameType`` for the provided type name associated with this service. + + :param name: the name of the ``NameType`` to get. + :raises KeyError: if ``name`` doesn't exist in this service. """ return self._name_types[name] diff --git a/src/zepben/ewb/services/common/base_service_comparator.py b/src/zepben/ewb/services/common/base_service_comparator.py index e6d6b7f9f..3203934e0 100644 --- a/src/zepben/ewb/services/common/base_service_comparator.py +++ b/src/zepben/ewb/services/common/base_service_comparator.py @@ -7,6 +7,7 @@ from typing import get_type_hints, Dict, Type, Callable, Any, TypeVar, Optional, Union, List, Tuple from zepben.ewb import BaseService, IdentifiedObject, Organisation, Document, OrganisationRole +from zepben.ewb.model.cim.iec61970.base.core.identifiable import Identifiable from zepben.ewb.model.cim.iec61970.base.core.name import Name from zepben.ewb.model.cim.iec61970.base.core.name_type import NameType from zepben.ewb.services.common.difference import ObjectDifference, Difference, ValueDifference, ReferenceDifference, CollectionDifference, IndexedDifference @@ -127,8 +128,13 @@ def _try_compare(self, source_type: Type, s: T, t: T) -> ObjectDifference: def _compare_organisation(self, source: Organisation, target: Organisation) -> ObjectDifference: return self._compare_identified_object(ObjectDifference(source, target)) + def _compare_identifiable(self, diff: ObjectDifference) -> ObjectDifference: + self._compare_values(diff, Identifiable.mrid) + return diff + def _compare_identified_object(self, diff: ObjectDifference) -> ObjectDifference: - self._compare_values(diff, IdentifiedObject.mrid, IdentifiedObject.name, IdentifiedObject.description) + self._compare_identifiable(diff) + self._compare_values(diff, IdentifiedObject.name, IdentifiedObject.description) self._compare_names(diff, IdentifiedObject.names) return diff diff --git a/src/zepben/ewb/services/common/difference.py b/src/zepben/ewb/services/common/difference.py index ad7a1e633..cf08e9e99 100644 --- a/src/zepben/ewb/services/common/difference.py +++ b/src/zepben/ewb/services/common/difference.py @@ -5,7 +5,7 @@ from dataclasses import dataclass, field from typing import Optional, Any, List, Dict, TypeVar, Generic -from zepben.ewb import IdentifiedObject +from zepben.ewb.model.cim.iec61970.base.core.identifiable import Identifiable T = TypeVar("T") @@ -40,8 +40,8 @@ def __str__(self): @dataclass() class ReferenceDifference(Difference): - source: Optional[IdentifiedObject] - target_value: Optional[IdentifiedObject] + source: Optional[Identifiable] + target_value: Optional[Identifiable] @dataclass() diff --git a/src/zepben/ewb/services/common/reference_resolvers.py b/src/zepben/ewb/services/common/reference_resolvers.py index c30cd53a2..7930f3652 100644 --- a/src/zepben/ewb/services/common/reference_resolvers.py +++ b/src/zepben/ewb/services/common/reference_resolvers.py @@ -78,7 +78,7 @@ from zepben.ewb.model.cim.iec61970.base.core.equipment_container import EquipmentContainer from zepben.ewb.model.cim.iec61970.base.core.feeder import Feeder from zepben.ewb.model.cim.iec61970.base.core.geographical_region import GeographicalRegion -from zepben.ewb.model.cim.iec61970.base.core.identified_object import IdentifiedObject +from zepben.ewb.model.cim.iec61970.base.core.identifiable import Identifiable from zepben.ewb.model.cim.iec61970.base.core.power_system_resource import PowerSystemResource from zepben.ewb.model.cim.iec61970.base.core.sub_geographical_region import SubGeographicalRegion from zepben.ewb.model.cim.iec61970.base.core.substation import Substation @@ -125,7 +125,7 @@ class ReferenceResolver(object): from_class: type to_class: type - resolve: Callable[[IdentifiedObject, IdentifiedObject], Any] + resolve: Callable[[Identifiable, Identifiable], None] def __eq__(self, other): return self.from_class is other.from_class and self.to_class is other.to_class and self.resolve is other.resolve @@ -140,16 +140,16 @@ def __hash__(self): @dataclass(frozen=True, eq=False, slots=True) class BoundReferenceResolver(object): """ - :var from_obj: Identified object from which to resolve the reference. + :var from_obj: Identifiable from which to resolve the reference. :var resolver: Reference resolver to use. :var reverse_resolver: Reference resolver to use for the reverse. """ - from_obj: IdentifiedObject + from_obj: Identifiable resolver: ReferenceResolver reverse_resolver: Optional[ReferenceResolver] def __eq__(self, other): - """ We only do a reference check for `from_obj` to avoid expensive equality checks on `IdentifiedObjects`. """ + """ We only do a reference check for `from_obj` to avoid expensive equality checks on `Identifiables`. """ if self.reverse_resolver is None and other.reverse_resolver is None: return self.from_obj is other.from_obj and self.resolver == other.resolver elif self.reverse_resolver is not None and other.reverse_resolver is not None: @@ -175,7 +175,7 @@ def __hash__(self): @dataclass(frozen=True, eq=False, slots=True) class UnresolvedReference(object): - from_ref: IdentifiedObject + from_ref: Identifiable to_mrid: str resolver: ReferenceResolver reverse_resolver: Optional[ReferenceResolver] = None diff --git a/src/zepben/ewb/services/common/translator/base_proto2cim.py b/src/zepben/ewb/services/common/translator/base_proto2cim.py index f25c0900f..eb08281d2 100644 --- a/src/zepben/ewb/services/common/translator/base_proto2cim.py +++ b/src/zepben/ewb/services/common/translator/base_proto2cim.py @@ -22,25 +22,38 @@ from zepben.protobuf.cim.iec61970.base.core.NameType_pb2 import NameType as PBNameType from zepben.protobuf.cim.iec61970.base.core.Name_pb2 import Name as PBName -from zepben.ewb import Document, IdentifiedObject, Organisation, OrganisationRole from zepben.ewb.dataclassy import dataclass +from zepben.ewb.model.cim.iec61968.common.document import Document +from zepben.ewb.model.cim.iec61968.common.organisation_role import OrganisationRole +from zepben.ewb.model.cim.iec61968.common.organisation import Organisation +from zepben.ewb.model.cim.iec61970.base.core.identifiable import Identifiable +from zepben.ewb.model.cim.iec61970.base.core.identified_object import IdentifiedObject from zepben.ewb.model.cim.iec61970.base.core.name_type import NameType from zepben.ewb.services.common import resolver from zepben.ewb.services.common.base_service import BaseService -TProtoToCimFunc = Callable[[Message, BaseService], Optional[IdentifiedObject]] +TProtoToCimFunc = Callable[[Message, BaseService], Optional[Identifiable]] P = ParamSpec("P") R = TypeVar("R") -def add_to_network_or_none(func: TProtoToCimFunc) -> TProtoToCimFunc: +def add_to_service_or_none(func: TProtoToCimFunc) -> TProtoToCimFunc: """ This should wrap any leaf class of the hierarchy, for example, If you're porting over ewb-sdk-jvm changes, any of the classes that get used in a `network.add(Class)` + + This decorator expects your function to return the Class to be added to the ``BaseService``. + + Unless you know what you're doing, this should be the "innermost" decorator. ie:: + + @bind_to_cim + @add_to_service_or_none + def cim_class_to_cim(pb: PBCimClass, service: BaseService) -> CimClass | None: + return CimClass() """ @functools.wraps(func) - def wrapper(pb: Message, service: BaseService) -> Optional[IdentifiedObject]: + def wrapper(pb: Message, service: BaseService) -> Optional[Identifiable]: return cim if service.add(cim := func(pb, service)) else None return wrapper @@ -48,7 +61,16 @@ def wrapper(pb: Message, service: BaseService) -> Optional[IdentifiedObject]: def bind_to_cim(func: Callable[P, R]) -> Callable[P, R]: """ Get the object described in the type hint of the first argument of the function we are wrapping - set that object's `to_cim` function to be the function we are wrapping + set that object's `to_cim` function to be the function we are wrapping so that the decorated + function can be called directly on the object, ie:: + + object.to_cim() + + This should be the "outermost" decorator. ie:: + + @bind_to_cim + def cim_class_to_cim(pb: PBCimClass, cim: CimClass, service: BaseService) -> CimClass | None: + cim.field = stuff() """ inspect.get_annotations(func, eval_str=True)[func.__code__.co_varnames[0]].to_cim = func return func @@ -76,7 +98,7 @@ def document_to_cim(pb: PBDocument, cim: Document, service: BaseService): @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def organisation_to_cim(pb: PBOrganisation, service: BaseService) -> Optional[Organisation]: cim = Organisation(mrid=pb.mrid()) @@ -109,7 +131,7 @@ def name_to_cim(pb: PBName, io: IdentifiedObject, service: BaseService): nt = service.get_name_type(pb.type) except KeyError: # noinspection PyArgumentList - nt = NameType(pb.type) + nt = NameType(name=pb.type) service.add_name_type(nt) return nt.get_or_add_name(pb.name, io) @@ -121,7 +143,7 @@ def name_type_to_cim(pb: PBNameType, service: BaseService): nt = service.get_name_type(pb.name) except KeyError: # noinspection PyArgumentList - nt = NameType(pb.name) + nt = NameType(name=pb.name) service.add_name_type(nt) nt.description = get_nullable(pb, 'description') @@ -134,7 +156,7 @@ class BaseProtoToCim(object, metaclass=ABCMeta): # Extensions -def _add_from_pb(service: BaseService, pb) -> Optional[IdentifiedObject]: +def _add_from_pb(service: BaseService, pb) -> Optional[Identifiable]: """Must only be called by objects for which .to_cim() takes themselves and the network service.""" try: return pb.to_cim(service) diff --git a/src/zepben/ewb/services/common/translator/service_differences.py b/src/zepben/ewb/services/common/translator/service_differences.py index 25ba4f9ab..2030f0486 100644 --- a/src/zepben/ewb/services/common/translator/service_differences.py +++ b/src/zepben/ewb/services/common/translator/service_differences.py @@ -4,7 +4,7 @@ # file, You can obtain one at https://mozilla.org/MPL/2.0/. from typing import Callable, Optional, Set, Dict, Generator, Tuple, Any, Iterable -from zepben.ewb import IdentifiedObject +from zepben.ewb.model.cim.iec61970.base.core.identifiable import Identifiable from zepben.ewb.model.cim.iec61970.base.core.name_type import NameType from zepben.ewb.services.common.difference import ObjectDifference @@ -13,8 +13,8 @@ class ServiceDifferences(object): def __init__( self, - source_lookup: Callable[[str], Optional[IdentifiedObject]], - target_lookup: Callable[[str], Optional[IdentifiedObject]], + source_lookup: Callable[[str], Optional[Identifiable]], + target_lookup: Callable[[str], Optional[Identifiable]], source_name_type_lookup: Callable[[str], Optional[NameType]], target_name_type_lookup: Callable[[str], Optional[NameType]] ): @@ -50,8 +50,8 @@ def add_modifications(self, mrid: str, difference: ObjectDifference): self._modifications[mrid] = difference def __str__(self) -> str: - return ("\nMissing From Target:" + _indent_each(self._missing_from_target, self._source_lookup, self._source_name_type_lookup) + \ - "\nMissing From Source:" + _indent_each(self._missing_from_source, self._target_lookup, self._target_name_type_lookup) + \ + return ("\nMissing From Target:" + _indent_each(self._missing_from_target, self._source_lookup, self._source_name_type_lookup) + + "\nMissing From Source:" + _indent_each(self._missing_from_source, self._target_lookup, self._target_name_type_lookup) + "\nModifications:" + ''.join(_indented_line(f"{k}: {_detailed_modifications(v)},") for k, v in self._modifications.items())) diff --git a/src/zepben/ewb/services/customer/translator/customer_proto2cim.py b/src/zepben/ewb/services/customer/translator/customer_proto2cim.py index a3feafe6b..d8129bb7f 100644 --- a/src/zepben/ewb/services/customer/translator/customer_proto2cim.py +++ b/src/zepben/ewb/services/customer/translator/customer_proto2cim.py @@ -17,7 +17,7 @@ from zepben.protobuf.cim.iec61970.base.domain.DateTimeInterval_pb2 import DateTimeInterval as PBDateTimeInterval import zepben.ewb.services.common.resolver as resolver -from zepben.ewb import organisation_role_to_cim, document_to_cim, CustomerKind, bind_to_cim +from zepben.ewb import organisation_role_to_cim, document_to_cim, CustomerKind, bind_to_cim, add_to_service_or_none from zepben.ewb.model.cim.iec61968.common.agreement import Agreement from zepben.ewb.model.cim.iec61968.customers.customer import Customer from zepben.ewb.model.cim.iec61968.customers.customer_agreement import CustomerAgreement @@ -48,6 +48,7 @@ def agreement_to_cim(pb: PBAgreement, cim: Agreement, service: CustomerService): ###################### @bind_to_cim +@add_to_service_or_none def customer_to_cim(pb: PBCustomer, service: CustomerService) -> Optional[Customer]: cim = Customer( mrid=pb.mrid(), @@ -60,10 +61,11 @@ def customer_to_cim(pb: PBCustomer, service: CustomerService) -> Optional[Custom service.resolve_or_defer_reference(resolver.agreements(cim), mrid) organisation_role_to_cim(getattr(pb, 'or'), cim, service) - return cim if service.add(cim) else None + return cim @bind_to_cim +@add_to_service_or_none def customer_agreement_to_cim(pb: PBCustomerAgreement, service: CustomerService) -> Optional[CustomerAgreement]: cim = CustomerAgreement(mrid=pb.mrid()) @@ -72,10 +74,11 @@ def customer_agreement_to_cim(pb: PBCustomerAgreement, service: CustomerService) service.resolve_or_defer_reference(resolver.pricing_structures(cim), mrid) agreement_to_cim(pb.agr, cim, service) - return cim if service.add(cim) else None + return cim @bind_to_cim +@add_to_service_or_none def pricing_structure_to_cim(pb: PBPricingStructure, service: CustomerService) -> Optional[PricingStructure]: cim = PricingStructure(mrid=pb.mrid()) @@ -85,16 +88,17 @@ def pricing_structure_to_cim(pb: PBPricingStructure, service: CustomerService) - cim.code = get_nullable(pb, 'code') document_to_cim(pb.doc, cim, service) - return cim if service.add(cim) else None + return cim @bind_to_cim +@add_to_service_or_none def tariff_to_cim(pb: PBTariff, service: CustomerService) -> Optional[Tariff]: # noinspection PyArgumentList cim = Tariff(mrid=pb.mrid()) document_to_cim(pb.doc, cim, service) - return cim if service.add(cim) else None + return cim ######################## diff --git a/src/zepben/ewb/services/diagram/translator/diagram_proto2cim.py b/src/zepben/ewb/services/diagram/translator/diagram_proto2cim.py index acba879d0..16100a4e8 100644 --- a/src/zepben/ewb/services/diagram/translator/diagram_proto2cim.py +++ b/src/zepben/ewb/services/diagram/translator/diagram_proto2cim.py @@ -10,7 +10,7 @@ from zepben.protobuf.cim.iec61970.base.diagramlayout.Diagram_pb2 import Diagram as PBDiagram import zepben.ewb.services.common.resolver as resolver -from zepben.ewb import identified_object_to_cim, OrientationKind, DiagramStyle, add_to_network_or_none, bind_to_cim +from zepben.ewb import identified_object_to_cim, OrientationKind, DiagramStyle, add_to_service_or_none, bind_to_cim from zepben.ewb.model.cim.iec61970.base.diagramlayout.diagram import Diagram from zepben.ewb.model.cim.iec61970.base.diagramlayout.diagram_object import DiagramObject from zepben.ewb.model.cim.iec61970.base.diagramlayout.diagram_object_point import DiagramObjectPoint @@ -23,7 +23,7 @@ ################################ @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def diagram_to_cim(pb: PBDiagram, service: DiagramService): cim = Diagram( mrid=pb.mrid(), diff --git a/src/zepben/ewb/services/network/translator/network_proto2cim.py b/src/zepben/ewb/services/network/translator/network_proto2cim.py index b84e35555..e37764041 100644 --- a/src/zepben/ewb/services/network/translator/network_proto2cim.py +++ b/src/zepben/ewb/services/network/translator/network_proto2cim.py @@ -340,7 +340,7 @@ from zepben.ewb.model.cim.iec61970.base.wires.transformer_star_impedance import * from zepben.ewb.model.cim.iec61970.base.wires.winding_connection import * from zepben.ewb.model.cim.iec61970.infiec61970.feeder.circuit import * -from zepben.ewb.services.common.translator.base_proto2cim import identified_object_to_cim, organisation_role_to_cim, document_to_cim, add_to_network_or_none, \ +from zepben.ewb.services.common.translator.base_proto2cim import identified_object_to_cim, organisation_role_to_cim, document_to_cim, add_to_service_or_none, \ bind_to_cim, get_nullable from zepben.ewb.services.common.translator.util import int_or_none, float_or_none, str_or_none from zepben.ewb.services.network.network_service import NetworkService @@ -352,7 +352,7 @@ ################################## @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def relay_info_to_cim(pb: PBRelayInfo, network_service: NetworkService) -> Optional[RelayInfo]: # noinspection PyUnresolvedReferences cim = RelayInfo( @@ -395,7 +395,7 @@ def contact_details_to_cim(pb: PBContactDetails, network_service: NetworkService ################################ @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def pan_demand_response_function_to_cim(pb: PBPanDemandResponseFunction, network_service: NetworkService) -> PanDemandResponseFunction: """ Convert the protobuf :class:`PBPanDemandResponseFunction` into its CIM counterpart. @@ -417,7 +417,7 @@ def pan_demand_response_function_to_cim(pb: PBPanDemandResponseFunction, network ################################# @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def hv_customer_to_cim(pb: PBHvCustomer, network_service: NetworkService) -> HvCustomer: # noinspection PyUnresolvedReferences cim = HvCustomer(mrid=pb.mrid()) @@ -426,7 +426,7 @@ def hv_customer_to_cim(pb: PBHvCustomer, network_service: NetworkService) -> HvC return cim @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def site_to_cim(pb: PBSite, network_service: NetworkService) -> Optional[Site]: # noinspection PyUnresolvedReferences cim = Site(mrid=pb.mrid()) @@ -440,7 +440,7 @@ def site_to_cim(pb: PBSite, network_service: NetworkService) -> Optional[Site]: ################################### @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def loop_to_cim(pb: PBLoop, network_service: NetworkService) -> Optional[Loop]: # noinspection PyUnresolvedReferences cim = Loop(mrid=pb.mrid()) @@ -457,7 +457,7 @@ def loop_to_cim(pb: PBLoop, network_service: NetworkService) -> Optional[Loop]: @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def lv_feeder_to_cim(pb: PBLvFeeder, network_service: NetworkService) -> Optional[LvFeeder]: # noinspection PyUnresolvedReferences cim = LvFeeder(mrid=pb.mrid()) @@ -474,7 +474,7 @@ def lv_feeder_to_cim(pb: PBLvFeeder, network_service: NetworkService) -> Optiona @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def lv_substation_to_cim(pb: PBLvSubstation, network_service: NetworkService) -> LvSubstation: # noinspection PyUnresolvedReferences cim = LvSubstation(mrid=pb.mrid()) @@ -494,7 +494,7 @@ def lv_substation_to_cim(pb: PBLvSubstation, network_service: NetworkService) -> ################################################## @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def ev_charging_unit_to_cim(pb: PBEvChargingUnit, network_service: NetworkService) -> Optional[EvChargingUnit]: # noinspection PyUnresolvedReferences cim = EvChargingUnit(mrid=pb.mrid()) @@ -506,7 +506,7 @@ def ev_charging_unit_to_cim(pb: PBEvChargingUnit, network_service: NetworkServic ####################################### @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def directional_current_relay_to_cim(pb: PBDirectionalCurrentRelay, network_service: NetworkService) -> DirectionalCurrentRelay | None: cim = DirectionalCurrentRelay( mrid=pb.prf.psr.io.mRID, @@ -524,7 +524,7 @@ def directional_current_relay_to_cim(pb: PBDirectionalCurrentRelay, network_serv @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def distance_relay_to_cim(pb: PBDistanceRelay, network_service: NetworkService) -> Optional[DistanceRelay]: # noinspection PyUnresolvedReferences cim = DistanceRelay( @@ -568,7 +568,7 @@ def protection_relay_function_to_cim(pb: PBProtectionRelayFunction, cim: Protect @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def protection_relay_scheme_to_cim(pb: PBProtectionRelayScheme, network_service: NetworkService) -> Optional[ProtectionRelayScheme]: # noinspection PyUnresolvedReferences cim = ProtectionRelayScheme( @@ -586,7 +586,7 @@ def protection_relay_scheme_to_cim(pb: PBProtectionRelayScheme, network_service: @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def protection_relay_system_to_cim(pb: PBProtectionRelaySystem, network_service: NetworkService) -> Optional[ProtectionRelaySystem]: # noinspection PyUnresolvedReferences cim = ProtectionRelaySystem( @@ -610,7 +610,7 @@ def relay_setting_to_cim(pb: PBRelaySetting) -> Optional[RelaySetting]: @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def voltage_relay_to_cim(pb: PBVoltageRelay, network_service: NetworkService) -> Optional[VoltageRelay]: # noinspection PyUnresolvedReferences cim = VoltageRelay(mrid=pb.mrid()) @@ -624,7 +624,7 @@ def voltage_relay_to_cim(pb: PBVoltageRelay, network_service: NetworkService) -> ################################## @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def battery_control_to_cim(pb: PBBatteryControl, network_service: NetworkService) -> BatteryControl: """ Convert the protobuf :class:`PBBatteryControl` into its CIM counterpart. @@ -652,7 +652,7 @@ def battery_control_to_cim(pb: PBBatteryControl, network_service: NetworkService @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def cable_info_to_cim(pb: PBCableInfo, network_service: NetworkService) -> Optional[CableInfo]: # noinspection PyUnresolvedReferences cim = CableInfo(mrid=pb.mrid()) @@ -662,7 +662,7 @@ def cable_info_to_cim(pb: PBCableInfo, network_service: NetworkService) -> Optio @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def no_load_test_to_cim(pb: PBNoLoadTest, network_service: NetworkService) -> Optional[NoLoadTest]: # noinspection PyUnresolvedReferences cim = NoLoadTest( @@ -679,7 +679,7 @@ def no_load_test_to_cim(pb: PBNoLoadTest, network_service: NetworkService) -> Op @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def open_circuit_test_to_cim(pb: PBOpenCircuitTest, network_service: NetworkService) -> Optional[OpenCircuitTest]: # noinspection PyUnresolvedReferences cim = OpenCircuitTest( @@ -696,7 +696,7 @@ def open_circuit_test_to_cim(pb: PBOpenCircuitTest, network_service: NetworkServ @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def overhead_wire_info_to_cim(pb: PBOverheadWireInfo, network_service: NetworkService) -> Optional[OverheadWireInfo]: # noinspection PyUnresolvedReferences cim = OverheadWireInfo(mrid=pb.mrid()) @@ -706,7 +706,7 @@ def overhead_wire_info_to_cim(pb: PBOverheadWireInfo, network_service: NetworkSe @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def power_transformer_info_to_cim(pb: PBPowerTransformerInfo, network_service: NetworkService) -> Optional[PowerTransformerInfo]: # noinspection PyUnresolvedReferences cim = PowerTransformerInfo(mrid=pb.mrid()) @@ -719,7 +719,7 @@ def power_transformer_info_to_cim(pb: PBPowerTransformerInfo, network_service: N @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def short_circuit_test_to_cim(pb: PBShortCircuitTest, network_service: NetworkService) -> Optional[ShortCircuitTest]: # noinspection PyUnresolvedReferences cim = ShortCircuitTest( @@ -741,7 +741,7 @@ def short_circuit_test_to_cim(pb: PBShortCircuitTest, network_service: NetworkSe @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def shunt_compensator_info_to_cim(pb: PBShuntCompensatorInfo, network_service: NetworkService) -> Optional[ShuntCompensatorInfo]: # noinspection PyUnresolvedReferences cim = ShuntCompensatorInfo( @@ -757,7 +757,7 @@ def shunt_compensator_info_to_cim(pb: PBShuntCompensatorInfo, network_service: N @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def switch_info_to_cim(pb: PBSwitchInfo, network_service: NetworkService) -> Optional[SwitchInfo]: # noinspection PyUnresolvedReferences cim = SwitchInfo( @@ -770,7 +770,7 @@ def switch_info_to_cim(pb: PBSwitchInfo, network_service: NetworkService) -> Opt @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def transformer_end_info_to_cim(pb: PBTransformerEndInfo, network_service: NetworkService) -> Optional[TransformerEndInfo]: # noinspection PyUnresolvedReferences cim = TransformerEndInfo( @@ -799,7 +799,7 @@ def transformer_end_info_to_cim(pb: PBTransformerEndInfo, network_service: Netwo @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def transformer_tank_info_to_cim(pb: PBTransformerTankInfo, network_service: NetworkService) -> Optional[TransformerTankInfo]: # noinspection PyUnresolvedReferences cim = TransformerTankInfo(mrid=pb.mrid()) @@ -872,7 +872,7 @@ def asset_organisation_role_to_cim(pb: PBAssetOrganisationRole, cim: AssetOrgani @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def asset_owner_to_cim(pb: PBAssetOwner, network_service: NetworkService) -> Optional[AssetOwner]: # noinspection PyUnresolvedReferences cim = AssetOwner(mrid=pb.mrid()) @@ -882,7 +882,7 @@ def asset_owner_to_cim(pb: PBAssetOwner, network_service: NetworkService) -> Opt @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def streetlight_to_cim(pb: PBStreetlight, network_service: NetworkService) -> Optional[Streetlight]: # noinspection PyUnresolvedReferences cim = Streetlight( @@ -914,7 +914,7 @@ def electronic_address_to_cim(pb: PBElectronicAddress, network_service: NetworkS ) @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def location_to_cim(pb: PBLocation, network_service: NetworkService) -> Optional[Location]: # noinspection PyUnresolvedReferences cim = Location(mrid=pb.mrid(), main_address=street_address_to_cim(pb.mainAddress) if pb.HasField("mainAddress") else None) @@ -978,7 +978,7 @@ def town_detail_to_cim(pb: PBTownDetail) -> Optional[TownDetail]: ##################################### @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def current_transformer_info_to_cim(pb: PBCurrentTransformerInfo, network_service: NetworkService) -> Optional[CurrentTransformerInfo]: # noinspection PyUnresolvedReferences cim = CurrentTransformerInfo( @@ -1002,7 +1002,7 @@ def current_transformer_info_to_cim(pb: PBCurrentTransformerInfo, network_servic @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def potential_transformer_info_to_cim(pb: PBPotentialTransformerInfo, network_service: NetworkService) -> Optional[PotentialTransformerInfo]: # noinspection PyUnresolvedReferences cim = PotentialTransformerInfo( @@ -1024,7 +1024,7 @@ def potential_transformer_info_to_cim(pb: PBPotentialTransformerInfo, network_se ################################## @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def pole_to_cim(pb: PBPole, network_service: NetworkService) -> Optional[Pole]: # noinspection PyUnresolvedReferences cim = Pole( @@ -1086,7 +1086,7 @@ def end_device_function_to_cim(pb: PBEndDeviceFunction, cim: EndDeviceFunction, @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def meter_to_cim(pb: PBMeter, network_service: NetworkService) -> Optional[Meter]: # noinspection PyUnresolvedReferences cim = Meter(mrid=pb.mrid()) @@ -1096,7 +1096,7 @@ def meter_to_cim(pb: PBMeter, network_service: NetworkService) -> Optional[Meter @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def usage_point_to_cim(pb: PBUsagePoint, network_service: NetworkService) -> Optional[UsagePoint]: # noinspection PyUnresolvedReferences cim = UsagePoint(mrid=pb.mrid()) @@ -1124,7 +1124,7 @@ def usage_point_to_cim(pb: PBUsagePoint, network_service: NetworkService) -> Opt ####################### @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def operational_restriction_to_cim(pb: PBOperationalRestriction, network_service: NetworkService) -> Optional[OperationalRestriction]: # noinspection PyUnresolvedReferences cim = OperationalRestriction(mrid=pb.mrid()) @@ -1143,7 +1143,7 @@ def auxiliary_equipment_to_cim(pb: PBAuxiliaryEquipment, cim: AuxiliaryEquipment @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def current_transformer_to_cim(pb: PBCurrentTransformer, network_service: NetworkService) -> Optional[CurrentTransformer]: # noinspection PyUnresolvedReferences cim = CurrentTransformer(mrid=pb.mrid(), core_burden=int_or_none(get_nullable(pb, 'coreBurden'))) @@ -1156,7 +1156,7 @@ def current_transformer_to_cim(pb: PBCurrentTransformer, network_service: Networ @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def fault_indicator_to_cim(pb: PBFaultIndicator, network_service: NetworkService) -> Optional[FaultIndicator]: # noinspection PyUnresolvedReferences cim = FaultIndicator(mrid=pb.mrid()) @@ -1166,7 +1166,7 @@ def fault_indicator_to_cim(pb: PBFaultIndicator, network_service: NetworkService @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def potential_transformer_to_cim(pb: PBPotentialTransformer, network_service: NetworkService) -> Optional[PotentialTransformer]: # noinspection PyUnresolvedReferences cim = PotentialTransformer(mrid=pb.mrid(), type=PotentialTransformerKind(pb.type)) @@ -1193,7 +1193,7 @@ def ac_dc_terminal_to_cim(pb: PBAcDcTerminal, cim: AcDcTerminal, network_service @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def base_voltage_to_cim(pb: PBBaseVoltage, network_service: NetworkService) -> Optional[BaseVoltage]: # noinspection PyUnresolvedReferences cim = BaseVoltage(mrid=pb.mrid(), nominal_voltage=pb.nominalVoltage) @@ -1211,7 +1211,7 @@ def conducting_equipment_to_cim(pb: PBConductingEquipment, cim: ConductingEquipm @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def connectivity_node_to_cim(pb: PBConnectivityNode, network_service: NetworkService) -> Optional[ConnectivityNode]: # noinspection PyUnresolvedReferences cim = ConnectivityNode(mrid=pb.mrid()) @@ -1265,7 +1265,7 @@ def equipment_container_to_cim(pb: PBEquipmentContainer, cim: EquipmentContainer @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def feeder_to_cim(pb: PBFeeder, network_service: NetworkService) -> Optional[Feeder]: # noinspection PyUnresolvedReferences cim = Feeder(mrid=pb.mrid()) @@ -1286,7 +1286,7 @@ def feeder_to_cim(pb: PBFeeder, network_service: NetworkService) -> Optional[Fee @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def geographical_region_to_cim(pb: PBGeographicalRegion, network_service: NetworkService) -> Optional[GeographicalRegion]: # noinspection PyUnresolvedReferences cim = GeographicalRegion(mrid=pb.mrid()) @@ -1308,7 +1308,7 @@ def power_system_resource_to_cim(pb: PBPowerSystemResource, cim: PowerSystemReso @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def sub_geographical_region_to_cim(pb: PBSubGeographicalRegion, network_service: NetworkService) -> Optional[SubGeographicalRegion]: # noinspection PyUnresolvedReferences cim = SubGeographicalRegion(mrid=pb.mrid()) @@ -1322,7 +1322,7 @@ def sub_geographical_region_to_cim(pb: PBSubGeographicalRegion, network_service: @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def substation_to_cim(pb: PBSubstation, network_service: NetworkService) -> Optional[Substation]: # noinspection PyUnresolvedReferences cim = Substation(mrid=pb.mrid()) @@ -1342,7 +1342,7 @@ def substation_to_cim(pb: PBSubstation, network_service: NetworkService) -> Opti @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def terminal_to_cim(pb: PBTerminal, network_service: NetworkService) -> Optional[Terminal]: # noinspection PyUnresolvedReferences cim = Terminal( @@ -1367,7 +1367,7 @@ def terminal_to_cim(pb: PBTerminal, network_service: NetworkService) -> Optional ############################# @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def equivalent_branch_to_cim(pb: PBEquivalentBranch, network_service: NetworkService) -> Optional[EquivalentBranch]: # noinspection PyUnresolvedReferences cim = EquivalentBranch( @@ -1403,7 +1403,7 @@ def equivalent_equipment_to_cim(pb: PBEquivalentEquipment, cim: EquivalentEquipm ####################################### @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def battery_unit_to_cim(pb: PBBatteryUnit, network_service: NetworkService) -> Optional[BatteryUnit]: """ Convert the protobuf :class:`PBBatteryUnit` into its CIM counterpart. @@ -1427,7 +1427,7 @@ def battery_unit_to_cim(pb: PBBatteryUnit, network_service: NetworkService) -> O @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def photo_voltaic_unit_to_cim(pb: PBPhotoVoltaicUnit, network_service: NetworkService) -> Optional[PhotoVoltaicUnit]: # noinspection PyUnresolvedReferences cim = PhotoVoltaicUnit(mrid=pb.mrid()) @@ -1446,7 +1446,7 @@ def power_electronics_unit_to_cim(pb: PBPowerElectronicsUnit, cim: PowerElectron @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def power_electronics_wind_unit_to_cim(pb: PBPowerElectronicsWindUnit, network_service: NetworkService) -> Optional[PowerElectronicsWindUnit]: # noinspection PyUnresolvedReferences cim = PowerElectronicsWindUnit(mrid=pb.mrid()) @@ -1460,7 +1460,7 @@ def power_electronics_wind_unit_to_cim(pb: PBPowerElectronicsWindUnit, network_s ###################### @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def accumulator_to_cim(pb: PBAccumulator, network_service: NetworkService) -> Optional[Accumulator]: # noinspection PyUnresolvedReferences cim = Accumulator(mrid=pb.mrid()) @@ -1470,7 +1470,7 @@ def accumulator_to_cim(pb: PBAccumulator, network_service: NetworkService) -> Op @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def analog_to_cim(pb: PBAnalog, network_service: NetworkService) -> Optional[Analog]: # noinspection PyUnresolvedReferences cim = Analog(mrid=pb.mrid(), positive_flow_in=get_nullable(pb, "positiveFlowIn")) @@ -1479,7 +1479,7 @@ def analog_to_cim(pb: PBAnalog, network_service: NetworkService) -> Optional[Ana return cim @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def control_to_cim(pb: PBControl, network_service: NetworkService) -> Optional[Control]: # noinspection PyUnresolvedReferences cim = Control( @@ -1495,7 +1495,7 @@ def control_to_cim(pb: PBControl, network_service: NetworkService) -> Optional[C @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def discrete_to_cim(pb: PBDiscrete, network_service: NetworkService) -> Optional[Discrete]: # noinspection PyUnresolvedReferences cim = Discrete(mrid=pb.mrid()) @@ -1524,7 +1524,7 @@ def measurement_to_cim(pb: PBMeasurement, cim: Measurement, service: NetworkServ ############################ @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def current_relay_to_cim(pb: PBCurrentRelay, network_service: NetworkService) -> Optional[CurrentRelay]: # noinspection PyUnresolvedReferences cim = CurrentRelay( @@ -1543,7 +1543,7 @@ def current_relay_to_cim(pb: PBCurrentRelay, network_service: NetworkService) -> ####################### @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def remote_control_to_cim(pb: PBRemoteControl, network_service: NetworkService) -> Optional[RemoteControl]: # noinspection PyUnresolvedReferences cim = RemoteControl(mrid=pb.mrid()) @@ -1559,7 +1559,7 @@ def remote_point_to_cim(pb: PBRemotePoint, cim: RemotePoint, service: NetworkSer @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def remote_source_to_cim(pb: PBRemoteSource, network_service: NetworkService) -> Optional[RemoteSource]: # noinspection PyUnresolvedReferences cim = RemoteSource(mrid=pb.mrid()) @@ -1575,7 +1575,7 @@ def remote_source_to_cim(pb: PBRemoteSource, network_service: NetworkService) -> ####################### @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def ac_line_segment_to_cim(pb: PBAcLineSegment, network_service: NetworkService) -> Optional[AcLineSegment]: """ Convert the protobuf :class:`PBAcLineSegment` into its CIM counterpart. @@ -1598,7 +1598,7 @@ def ac_line_segment_to_cim(pb: PBAcLineSegment, network_service: NetworkService) return cim @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def ac_line_segment_phase_to_cim(pb: PBAcLineSegmentPhase, network_service: NetworkService) -> AcLineSegmentPhase: # noinspection PyUnresolvedReferences cim = AcLineSegmentPhase(mrid=pb.mrid()) @@ -1611,7 +1611,7 @@ def ac_line_segment_phase_to_cim(pb: PBAcLineSegmentPhase, network_service: Netw return cim @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def breaker_to_cim(pb: PBBreaker, network_service: NetworkService) -> Optional[Breaker]: # noinspection PyUnresolvedReferences cim = Breaker( @@ -1624,7 +1624,7 @@ def breaker_to_cim(pb: PBBreaker, network_service: NetworkService) -> Optional[B @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def busbar_section_to_cim(pb: PBBusbarSection, network_service: NetworkService) -> Optional[BusbarSection]: # noinspection PyUnresolvedReferences cim = BusbarSection(mrid=pb.mrid()) @@ -1634,7 +1634,7 @@ def busbar_section_to_cim(pb: PBBusbarSection, network_service: NetworkService) @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def clamp_to_cim(pb: PBClamp, network_service: NetworkService) -> Optional[Clamp]: # noinspection PyUnresolvedReferences cim = Clamp(mrid=pb.mrid()) @@ -1662,7 +1662,7 @@ def connector_to_cim(pb: PBConnector, cim: Connector, network_service: NetworkSe @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def cut_to_cim(pb: PBCut, network_service: NetworkService) -> Optional[Cut]: # noinspection PyUnresolvedReferences cim = Cut(mrid=pb.mrid()) @@ -1675,7 +1675,7 @@ def cut_to_cim(pb: PBCut, network_service: NetworkService) -> Optional[Cut]: @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def disconnector_to_cim(pb: PBDisconnector, network_service: NetworkService) -> Optional[Disconnector]: # noinspection PyUnresolvedReferences cim = Disconnector(mrid=pb.mrid()) @@ -1695,7 +1695,7 @@ def energy_connection_to_cim(pb: PBEnergyConnection, cim: EnergyConnection, netw @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def energy_consumer_to_cim(pb: PBEnergyConsumer, network_service: NetworkService) -> Optional[EnergyConsumer]: # noinspection PyUnresolvedReferences cim = EnergyConsumer( @@ -1717,7 +1717,7 @@ def energy_consumer_to_cim(pb: PBEnergyConsumer, network_service: NetworkService @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def energy_consumer_phase_to_cim(pb: PBEnergyConsumerPhase, network_service: NetworkService) -> Optional[EnergyConsumerPhase]: # noinspection PyUnresolvedReferences cim = EnergyConsumerPhase( @@ -1736,7 +1736,7 @@ def energy_consumer_phase_to_cim(pb: PBEnergyConsumerPhase, network_service: Net @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def energy_source_to_cim(pb: PBEnergySource, network_service: NetworkService) -> Optional[EnergySource]: # noinspection PyUnresolvedReferences cim = EnergySource( @@ -1776,7 +1776,7 @@ def energy_source_to_cim(pb: PBEnergySource, network_service: NetworkService) -> @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def energy_source_phase_to_cim(pb: PBEnergySourcePhase, network_service: NetworkService) -> Optional[EnergySourcePhase]: # noinspection PyUnresolvedReferences cim = EnergySourcePhase(mrid=pb.mrid(), phase=single_phase_kind_by_id(pb.phase)) @@ -1788,7 +1788,7 @@ def energy_source_phase_to_cim(pb: PBEnergySourcePhase, network_service: Network @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def fuse_to_cim(pb: PBFuse, network_service: NetworkService) -> Optional[Fuse]: # noinspection PyUnresolvedReferences cim = Fuse(mrid=pb.mrid()) @@ -1800,7 +1800,7 @@ def fuse_to_cim(pb: PBFuse, network_service: NetworkService) -> Optional[Fuse]: @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def ground_to_cim(pb: PBGround, network_service: NetworkService) -> Optional[Ground]: # noinspection PyUnresolvedReferences cim = Ground(mrid=pb.mrid()) @@ -1810,7 +1810,7 @@ def ground_to_cim(pb: PBGround, network_service: NetworkService) -> Optional[Gro @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def ground_disconnector_to_cim(pb: PBGroundDisconnector, network_service: NetworkService) -> Optional[GroundDisconnector]: # noinspection PyUnresolvedReferences cim = GroundDisconnector(mrid=pb.mrid()) @@ -1820,7 +1820,7 @@ def ground_disconnector_to_cim(pb: PBGroundDisconnector, network_service: Networ @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def grounding_impedance_to_cim(pb: PBGroundingImpedance, network_service: NetworkService) -> Optional[GroundingImpedance]: # noinspection PyUnresolvedReferences cim = GroundingImpedance(mrid=pb.mrid(), x=get_nullable(pb, "x")) @@ -1830,7 +1830,7 @@ def grounding_impedance_to_cim(pb: PBGroundingImpedance, network_service: Networ @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def jumper_to_cim(pb: PBJumper, network_service: NetworkService) -> Optional[Jumper]: # noinspection PyUnresolvedReferences cim = Jumper(mrid=pb.mrid()) @@ -1840,7 +1840,7 @@ def jumper_to_cim(pb: PBJumper, network_service: NetworkService) -> Optional[Jum @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def junction_to_cim(pb: PBJunction, network_service: NetworkService) -> Optional[Junction]: # noinspection PyUnresolvedReferences cim = Junction(mrid=pb.mrid()) @@ -1854,7 +1854,7 @@ def line_to_cim(pb: PBLine, cim: Line, network_service: NetworkService): @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def linear_shunt_compensator_to_cim(pb: PBLinearShuntCompensator, network_service: NetworkService) -> Optional[LinearShuntCompensator]: # noinspection PyUnresolvedReferences cim = LinearShuntCompensator( @@ -1870,7 +1870,7 @@ def linear_shunt_compensator_to_cim(pb: PBLinearShuntCompensator, network_servic @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def load_break_switch_to_cim(pb: PBLoadBreakSwitch, network_service: NetworkService) -> Optional[LoadBreakSwitch]: # noinspection PyUnresolvedReferences cim = LoadBreakSwitch(mrid=pb.mrid()) @@ -1888,7 +1888,7 @@ def per_length_impedance_to_cim(pb: PBPerLengthImpedance, cim: PerLengthImpedanc @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def per_length_phase_impedance_to_cim(pb: PBPerLengthPhaseImpedance, network_service: NetworkService) -> Optional[PerLengthPhaseImpedance]: """ Convert the protobuf :class:`PBPerLengthPhaseImpedance` into its CIM counterpart. @@ -1907,7 +1907,7 @@ def per_length_phase_impedance_to_cim(pb: PBPerLengthPhaseImpedance, network_ser @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def per_length_sequence_impedance_to_cim(pb: PBPerLengthSequenceImpedance, network_service: NetworkService) -> Optional[PerLengthSequenceImpedance]: # noinspection PyUnresolvedReferences cim = PerLengthSequenceImpedance( @@ -1927,7 +1927,7 @@ def per_length_sequence_impedance_to_cim(pb: PBPerLengthSequenceImpedance, netwo @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def petersen_coil_to_cim(pb: PBPetersenCoil, network_service: NetworkService) -> Optional[PetersenCoil]: # noinspection PyUnresolvedReferences cim = PetersenCoil(mrid=pb.mrid(), x_ground_nominal=get_nullable(pb, 'xGroundNominal')) @@ -1953,7 +1953,7 @@ def phase_impedance_data_to_cim(pb: PBPhaseImpedanceData) -> Optional[PhaseImped @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def power_electronics_connection_to_cim(pb: PBPowerElectronicsConnection, network_service: NetworkService) -> Optional[PowerElectronicsConnection]: # noinspection PyUnresolvedReferences cim = PowerElectronicsConnection( @@ -2001,7 +2001,7 @@ def power_electronics_connection_to_cim(pb: PBPowerElectronicsConnection, networ @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def power_electronics_connection_phase_to_cim( pb: PBPowerElectronicsConnectionPhase, network_service: NetworkService @@ -2021,7 +2021,7 @@ def power_electronics_connection_phase_to_cim( @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def power_transformer_to_cim(pb: PBPowerTransformer, network_service: NetworkService) -> Optional[PowerTransformer]: # noinspection PyUnresolvedReferences cim = PowerTransformer( @@ -2042,7 +2042,7 @@ def power_transformer_to_cim(pb: PBPowerTransformer, network_service: NetworkSer @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def power_transformer_end_to_cim(pb: PBPowerTransformerEnd, network_service: NetworkService) -> Optional[PowerTransformerEnd]: # noinspection PyUnresolvedReferences cim = PowerTransformerEnd( @@ -2080,7 +2080,7 @@ def protected_switch_to_cim(pb: PBProtectedSwitch, cim: ProtectedSwitch, network @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def ratio_tap_changer_to_cim(pb: PBRatioTapChanger, network_service: NetworkService) -> Optional[RatioTapChanger]: # noinspection PyUnresolvedReferences cim = RatioTapChanger( @@ -2095,7 +2095,7 @@ def ratio_tap_changer_to_cim(pb: PBRatioTapChanger, network_service: NetworkServ @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def reactive_capability_curve_to_cim(pb: PBReactiveCapabilityCurve, network_service: NetworkService) -> Optional[ReactiveCapabilityCurve]: # noinspection PyUnresolvedReferences cim = ReactiveCapabilityCurve(mrid=pb.mrid()) @@ -2105,7 +2105,7 @@ def reactive_capability_curve_to_cim(pb: PBReactiveCapabilityCurve, network_serv @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def recloser_to_cim(pb: PBRecloser, network_service: NetworkService) -> Optional[Recloser]: # noinspection PyUnresolvedReferences cim = Recloser(mrid=pb.mrid()) @@ -2151,7 +2151,7 @@ def rotating_machine_to_cim(pb: PBRotatingMachine, cim: RotatingMachine, network @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def series_compensator_to_cim(pb: PBSeriesCompensator, network_service: NetworkService) -> Optional[SeriesCompensator]: # noinspection PyUnresolvedReferences cim = SeriesCompensator( @@ -2181,7 +2181,7 @@ def shunt_compensator_to_cim(pb: PBShuntCompensator, cim: ShuntCompensator, netw @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def static_var_compensator_to_cim(pb: PBStaticVarCompensator, network_service: NetworkService): """ Convert the protobuf :class:`PBStaticVarCompensator` into its CIM counterpart. @@ -2214,7 +2214,7 @@ def switch_to_cim(pb: PBSwitch, cim: Switch, network_service: NetworkService): @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def synchronous_machine_to_cim(pb: PBSynchronousMachine, network_service: NetworkService) -> Optional[SynchronousMachine]: # noinspection PyUnresolvedReferences cim = SynchronousMachine( @@ -2263,7 +2263,7 @@ def tap_changer_to_cim(pb: PBTapChanger, cim: TapChanger, network_service: Netwo @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def tap_changer_control_to_cim(pb: PBTapChangerControl, network_service: NetworkService) -> Optional[TapChangerControl]: # noinspection PyUnresolvedReferences cim = TapChangerControl( @@ -2302,7 +2302,7 @@ def transformer_end_rated_s_to_cim(pb: PBTransformerEndRatedS) -> Optional[Trans @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def transformer_star_impedance_to_cim(pb: PBTransformerStarImpedance, network_service: NetworkService) -> Optional[TransformerStarImpedance]: # noinspection PyUnresolvedReferences cim = TransformerStarImpedance( @@ -2324,7 +2324,7 @@ def transformer_star_impedance_to_cim(pb: PBTransformerStarImpedance, network_se ############################### @bind_to_cim -@add_to_network_or_none +@add_to_service_or_none def circuit_to_cim(pb: PBCircuit, network_service: NetworkService) -> Optional[Circuit]: # noinspection PyUnresolvedReferences cim = Circuit(mrid=pb.mrid()) diff --git a/src/zepben/ewb/streaming/get/consumer.py b/src/zepben/ewb/streaming/get/consumer.py index 826ee6aa9..ace4abebd 100644 --- a/src/zepben/ewb/streaming/get/consumer.py +++ b/src/zepben/ewb/streaming/get/consumer.py @@ -8,31 +8,35 @@ __all__ = ["CimConsumerClient", "MultiObjectResult"] from abc import abstractmethod -from typing import Iterable, Dict, Set, TypeVar, Generic, Tuple, Optional, AsyncGenerator, Type, Generator +from typing import Iterable, Dict, Set, TypeVar, Generic, Tuple, AsyncGenerator, Type, Generator, cast +from typing_extensions import deprecated from zepben.protobuf.metadata.metadata_requests_pb2 import GetMetadataRequest from zepben.protobuf.metadata.metadata_responses_pb2 import GetMetadataResponse -from zepben.ewb import BaseService, IdentifiedObject, UnsupportedOperationException, ServiceInfo +from zepben.ewb import BaseService, UnsupportedOperationException, ServiceInfo +from zepben.ewb.model.cim.iec61970.base.core.identifiable import Identifiable +from zepben.ewb.model.cim.iec61970.base.core.identified_object import IdentifiedObject from zepben.ewb.dataclassy import dataclass from zepben.ewb.services.common.meta.metadata_translations import service_info_from_pb from zepben.ewb.streaming.grpc.grpc import GrpcClient, GrpcResult -T = TypeVar('T', bound=IdentifiedObject) +T = TypeVar('T', bound=Identifiable) +TStub = TypeVar('TStub') @dataclass() -class MultiObjectResult(object): +class MultiObjectResult(Generic[T]): objects: Dict[str, T] = dict() failed: Set[str] = set() ServiceType = TypeVar('ServiceType', bound=BaseService) -PBIdentifiedObject = TypeVar('PBIdentifiedObject') +PBIdentifiable = TypeVar('PBIdentifiable') GrpcRequest = TypeVar('GrpcRequest') -class CimConsumerClient(GrpcClient, Generic[ServiceType]): +class CimConsumerClient(GrpcClient[TStub], Generic[ServiceType, TStub]): """ Base class that defines some helpful functions when producer clients are sending to the server. @@ -46,7 +50,7 @@ class CimConsumerClient(GrpcClient, Generic[ServiceType]): T: The base service to send objects from. """ - __service_info: Optional[ServiceInfo] + __service_info: ServiceInfo | None def __init__(self, **kwargs): super().__init__(**kwargs) @@ -60,6 +64,37 @@ def service(self) -> ServiceType: """ raise NotImplementedError() + async def get_identifiable(self, mrid: str) -> GrpcResult[Identifiable]: + """ + Retrieve the object with the given `mRID` and store the result in the `service`. + + Exceptions that occur during sending will be caught and passed to all error handlers that have been registered by `addErrorHandler`. + + Returns a :class:`GrpcResult` with a result of one of the following: + - When `GrpcResult.wasSuccessful`, the item found, accessible via `GrpcResult.value`. + - When `GrpcResult.wasFailure`, the error that occurred retrieving or processing the object, accessible via `GrpcResult.thrown`. One of: + - :class:`NoSuchElementException` if the object could not be found. + - The gRPC error that occurred while retrieving the object + """ + return await self._get_identifiable(mrid) + + async def get_identifiables(self, mrids: Iterable[str]) -> GrpcResult[MultiObjectResult]: + """ + Retrieve the objects with the given `mRIDs` and store the results in the `service`. + + Exceptions that occur during processing will be caught and passed to all error handlers that have been registered by `addErrorHandler`. + + @return A :class:`GrpcResult` with a result of one of the following: + - When `GrpcResult.wasSuccessful`, a map containing the retrieved objects keyed by mRID, accessible via `GrpcResult.value`. If an item was not + found, or couldn't be added to `service`, it will be excluded from the map and its mRID will be present in `MultiObjectResult.failed` + (see `BaseService.add`). + - When `GrpcResult.wasFailure`, the error that occurred retrieving or processing the object, accessible via `GrpcResult.thrown`. + + Note the :class:`CimConsumerClient` warning in this case. + """ + return await self._get_identifiables(mrids) + + @deprecated("Use get_identifiable() instead") async def get_identified_object(self, mrid: str) -> GrpcResult[IdentifiedObject]: """ Retrieve the object with the given `mRID` and store the result in the `service`. @@ -72,8 +107,9 @@ async def get_identified_object(self, mrid: str) -> GrpcResult[IdentifiedObject] - :class:`NoSuchElementException` if the object could not be found. - The gRPC error that occurred while retrieving the object """ - return await self._get_identified_object(mrid) + return cast(GrpcResult[IdentifiedObject], await self._get_identifiable(mrid)) + @deprecated("Use get_identifiables() instead") async def get_identified_objects(self, mrids: Iterable[str]) -> GrpcResult[MultiObjectResult]: """ Retrieve the objects with the given `mRIDs` and store the results in the `service`. @@ -88,25 +124,25 @@ async def get_identified_objects(self, mrids: Iterable[str]) -> GrpcResult[Multi Note the :class:`CimConsumerClient` warning in this case. """ - return await self._get_identified_objects(mrids) + return await self._get_identifiables(mrids) - async def _get_identified_object(self, mrid: str) -> GrpcResult[Optional[IdentifiedObject]]: + async def _get_identifiable(self, mrid: str) -> GrpcResult[Identifiable | None]: async def rpc(): - async for io, _ in self._process_identified_objects([mrid]): + async for io, _ in self._process_identifiables([mrid]): return io else: raise ValueError(f"No object with mRID {mrid} could be found.") return await self.try_rpc(rpc) - async def _get_identified_objects(self, mrids: Iterable[str]) -> GrpcResult[MultiObjectResult]: + async def _get_identifiables(self, mrids: Iterable[str]) -> GrpcResult[MultiObjectResult]: async def rpc(): - return await self._process_extract_results(mrids, self._process_identified_objects(set(mrids))) + return await self._process_extract_results(mrids, self._process_identifiables(set(mrids))) return await self.try_rpc(rpc) @abstractmethod - async def _process_identified_objects(self, mrids: Iterable[str]) -> AsyncGenerator[Tuple[Optional[IdentifiedObject], str], None]: + async def _process_identifiables(self, mrids: Iterable[str]) -> AsyncGenerator[Tuple[Identifiable, str] | None, None]: # # NOTE: this is a stupid test that is meant to fail to make sure we never yield, but we need to have the yield to make it return the generator. # @@ -114,28 +150,28 @@ async def _process_identified_objects(self, mrids: Iterable[str]) -> AsyncGenera yield raise NotImplementedError() - CIM_TYPE = TypeVar("CIM_TYPE", bound=IdentifiedObject) + CIM_TYPE = TypeVar("CIM_TYPE", bound=Identifiable) - def _extract_identified_object(self, - desc: str, - pb_io: PBIdentifiedObject, - pb_type_to_cim: Dict[str, Type[CIM_TYPE]], - check_presence: bool = True) -> Tuple[Optional[IdentifiedObject], str]: + def _extract_identifiable(self, + desc: str, + pb_io: PBIdentifiable, + pb_type_to_cim: Dict[str, Type[CIM_TYPE]], + check_presence: bool = True) -> Tuple[Identifiable | None, str]: """ - Add a :class:`CustomerIdentifiedObject` to the service. Will convert from protobuf to CIM type. + Add a :class:`CustomerIdentifiable` to the service. Will convert from protobuf to CIM type. Parameters - - `pb_io` - The wrapped identified object returned by the server. - - `pb_type_to_cim` - The mapping of wrapped identified object types to CIM objects. + - `pb_io` - The wrapped identifiable returned by the server. + - `pb_type_to_cim` - The mapping of wrapped identifiable types to CIM objects. - `check_presence` - Whether to check if `cio` already exists in the service and skip if it does. Raises :class:`UnsupportedOperationException` if `pb_io` was invalid/unset. """ - io_type = pb_io.WhichOneof("identifiedObject") + io_type = pb_io.WhichOneof("identifiable") if io_type: cim_type = pb_type_to_cim.get(io_type, None) if cim_type is None: - raise UnsupportedOperationException(f"Identified object type '{io_type}' is not supported by the {desc} service") + raise UnsupportedOperationException(f"Identifiable type '{io_type}' is not supported by the {desc} service") pb = getattr(pb_io, io_type) if check_presence: @@ -149,11 +185,13 @@ def _extract_identified_object(self, # noinspection PyUnresolvedReferences return self.service.add_from_pb(pb), pb.mrid() else: - raise UnsupportedOperationException(f"Received a {desc} identified object where no field was set") + raise UnsupportedOperationException(f"Received a {desc} identifiable where no field was set") @staticmethod - async def _process_extract_results(mrids: Optional[Iterable[str]], - extracted: AsyncGenerator[Tuple[Optional[IdentifiedObject], str], None]) -> MultiObjectResult: + async def _process_extract_results( + mrids: Iterable[str] | None, + extracted: AsyncGenerator[Tuple[Identifiable | None, str], None] + ) -> MultiObjectResult: results = {} if mrids is None: failed = set() diff --git a/src/zepben/ewb/streaming/get/customer_consumer.py b/src/zepben/ewb/streaming/get/customer_consumer.py index 9c6007489..9ffda0d68 100644 --- a/src/zepben/ewb/streaming/get/customer_consumer.py +++ b/src/zepben/ewb/streaming/get/customer_consumer.py @@ -8,19 +8,20 @@ __all__ = ["CustomerConsumerClient", "SyncCustomerConsumerClient"] from asyncio import get_event_loop -from typing import Optional, Iterable, AsyncGenerator, List, Callable, Tuple +from typing import Iterable, AsyncGenerator, List, Callable, Tuple from zepben.protobuf.cc.cc_pb2_grpc import CustomerConsumerStub -from zepben.protobuf.cc.cc_requests_pb2 import GetIdentifiedObjectsRequest, GetCustomersForContainerRequest +from zepben.protobuf.cc.cc_requests_pb2 import GetIdentifiablesRequest, GetCustomersForContainerRequest from zepben.protobuf.metadata.metadata_requests_pb2 import GetMetadataRequest from zepben.protobuf.metadata.metadata_responses_pb2 import GetMetadataResponse -from zepben.ewb import CustomerService, IdentifiedObject, Organisation, Customer, CustomerAgreement, PricingStructure, Tariff, ServiceInfo +from zepben.ewb import CustomerService, Organisation, Customer, CustomerAgreement, PricingStructure, Tariff, ServiceInfo +from zepben.ewb.model.cim.iec61970.base.core.identifiable import Identifiable from zepben.ewb.streaming.get.consumer import CimConsumerClient, MultiObjectResult from zepben.ewb.streaming.grpc.grpc import GrpcResult -class CustomerConsumerClient(CimConsumerClient[CustomerService]): +class CustomerConsumerClient(CimConsumerClient[CustomerService, CustomerConsumerStub]): """ Consumer client for a :class:`CustomerService`. @@ -37,16 +38,13 @@ class CustomerConsumerClient(CimConsumerClient[CustomerService]): def service(self) -> CustomerService: return self.__service - _stub: CustomerConsumerStub = None - def __init__(self, channel=None, stub: CustomerConsumerStub = None, error_handlers: List[Callable[[Exception], bool]] = None, timeout: int = 60): - super().__init__(error_handlers=error_handlers, timeout=timeout) - if channel is None and stub is None: - raise ValueError("Must provide either a channel or a stub") if stub is not None: - self._stub = stub + super().__init__(error_handlers=error_handlers, timeout=timeout, stub=stub) + elif channel is not None: + super().__init__(error_handlers=error_handlers, timeout=timeout, stub=CustomerConsumerStub(channel)) else: - self._stub = CustomerConsumerStub(channel) + raise ValueError("Must provide either a channel or a stub") self.__service = CustomerService() @@ -65,32 +63,32 @@ async def rpc(): return await self.try_rpc(rpc) - async def _process_customers_for_containers(self, mrids: Iterable[str]) -> AsyncGenerator[Tuple[Optional[IdentifiedObject], str], None]: + async def _process_customers_for_containers(self, mrids: Iterable[str]) -> AsyncGenerator[Tuple[Identifiable | None, str], None]: if not mrids: return responses = self._stub.getCustomersForContainer(self._batch_send(GetCustomersForContainerRequest(), mrids), timeout=self.timeout) async for response in responses: - for cio in response.identifiedObjects: - yield self._extract_identified_object("customer", cio, _cio_type_to_cim) + for cio in response.identifiables: + yield self._extract_identifiable("customer", cio, _cio_type_to_cim) - async def _process_identified_objects(self, mrids: Iterable[str]) -> AsyncGenerator[Tuple[Optional[IdentifiedObject], str], None]: + async def _process_identifiables(self, mrids: Iterable[str]) -> AsyncGenerator[Tuple[Identifiable | None, str], None]: if not mrids: return - responses = self._stub.getIdentifiedObjects(self._batch_send(GetIdentifiedObjectsRequest(), mrids), timeout=self.timeout) + responses = self._stub.getIdentifiables(self._batch_send(GetIdentifiablesRequest(), mrids), timeout=self.timeout) async for response in responses: - for cio in response.identifiedObjects: - yield self._extract_identified_object("customer", cio, _cio_type_to_cim) + for cio in response.identifiables: + yield self._extract_identifiable("customer", cio, _cio_type_to_cim) class SyncCustomerConsumerClient(CustomerConsumerClient): - def get_identified_object(self, mrid: str) -> GrpcResult[Optional[IdentifiedObject]]: - return get_event_loop().run_until_complete(super()._get_identified_objects(mrid)) + def get_identifiable(self, mrid: str) -> GrpcResult[Identifiable | None]: + return get_event_loop().run_until_complete(super()._get_identifiable(mrid)) - def get_identified_objects(self, mrids: Iterable[str]) -> GrpcResult[MultiObjectResult]: - return get_event_loop().run_until_complete(super()._get_identified_objects(mrids)) + def get_identifiables(self, mrids: Iterable[str]) -> GrpcResult[MultiObjectResult]: + return get_event_loop().run_until_complete(super()._get_identifiables(mrids)) def get_customers_for_container(self, mrid: str) -> GrpcResult[MultiObjectResult]: return get_event_loop().run_until_complete(super()._get_customers_for_containers({mrid})) diff --git a/src/zepben/ewb/streaming/get/diagram_consumer.py b/src/zepben/ewb/streaming/get/diagram_consumer.py index 2f19cc431..64f08d1ce 100644 --- a/src/zepben/ewb/streaming/get/diagram_consumer.py +++ b/src/zepben/ewb/streaming/get/diagram_consumer.py @@ -8,21 +8,22 @@ __all__ = ["DiagramConsumerClient", "SyncDiagramConsumerClient"] from asyncio import get_event_loop -from typing import Optional, Iterable, AsyncGenerator, List, Callable, Tuple, Union +from typing import Iterable, AsyncGenerator, List, Callable, Tuple, Union from zepben.protobuf.dc.dc_pb2_grpc import DiagramConsumerStub -from zepben.protobuf.dc.dc_requests_pb2 import GetIdentifiedObjectsRequest, GetDiagramObjectsRequest +from zepben.protobuf.dc.dc_requests_pb2 import GetIdentifiablesRequest, GetDiagramObjectsRequest from zepben.protobuf.metadata.metadata_requests_pb2 import GetMetadataRequest from zepben.protobuf.metadata.metadata_responses_pb2 import GetMetadataResponse -from zepben.ewb import DiagramService, IdentifiedObject, ServiceInfo +from zepben.ewb import DiagramService, ServiceInfo +from zepben.ewb.model.cim.iec61970.base.core.identifiable import Identifiable from zepben.ewb.model.cim.iec61970.base.diagramlayout.diagram import Diagram from zepben.ewb.model.cim.iec61970.base.diagramlayout.diagram_object import DiagramObject from zepben.ewb.streaming.get.consumer import CimConsumerClient, MultiObjectResult from zepben.ewb.streaming.grpc.grpc import GrpcResult -class DiagramConsumerClient(CimConsumerClient[DiagramService]): +class DiagramConsumerClient(CimConsumerClient[DiagramService, DiagramConsumerStub]): """ Consumer client for a :class:`DiagramService`. @@ -39,16 +40,13 @@ class DiagramConsumerClient(CimConsumerClient[DiagramService]): def service(self) -> DiagramService: return self.__service - _stub: DiagramConsumerStub = None - def __init__(self, channel=None, stub: DiagramConsumerStub = None, error_handlers: List[Callable[[Exception], bool]] = None, timeout: int = 60): - super().__init__(error_handlers=error_handlers, timeout=timeout) - if channel is None and stub is None: - raise ValueError("Must provide either a channel or a stub") if stub is not None: - self._stub = stub + super().__init__(error_handlers=error_handlers, timeout=timeout, stub=stub) + elif channel is not None: + super().__init__(error_handlers=error_handlers, timeout=timeout, stub=DiagramConsumerStub(channel)) else: - self._stub = DiagramConsumerStub(channel) + raise ValueError("Must provide either a channel or a stub") self.__service = DiagramService() @@ -67,32 +65,32 @@ async def rpc(): return await self.try_rpc(rpc) - async def _process_diagram_objects(self, mrids: Iterable[str]) -> AsyncGenerator[Tuple[Optional[IdentifiedObject], str], None]: + async def _process_diagram_objects(self, mrids: Iterable[str]) -> AsyncGenerator[Tuple[Identifiable | None, str], None]: if not mrids: return responses = self._stub.getDiagramObjects(self._batch_send(GetDiagramObjectsRequest(), mrids), timeout=self.timeout) async for response in responses: - for dio in response.identifiedObjects: - yield self._extract_identified_object("diagram", dio, _dio_type_to_cim) + for dio in response.identifiables: + yield self._extract_identifiable("diagram", dio, _dio_type_to_cim) - async def _process_identified_objects(self, mrids: Iterable[str]) -> AsyncGenerator[Tuple[Optional[IdentifiedObject], str], None]: + async def _process_identifiables(self, mrids: Iterable[str]) -> AsyncGenerator[Tuple[Identifiable | None, str], None]: if not mrids: return - responses = self._stub.getIdentifiedObjects(self._batch_send(GetIdentifiedObjectsRequest(), mrids), timeout=self.timeout) + responses = self._stub.getIdentifiables(self._batch_send(GetIdentifiablesRequest(), mrids), timeout=self.timeout) async for response in responses: - for dio in response.identifiedObjects: - yield self._extract_identified_object("diagram", dio, _dio_type_to_cim) + for dio in response.identifiables: + yield self._extract_identifiable("diagram", dio, _dio_type_to_cim) class SyncDiagramConsumerClient(DiagramConsumerClient): - def get_identified_object(self, mrid: str) -> GrpcResult[Optional[IdentifiedObject]]: - return get_event_loop().run_until_complete(super()._get_identified_objects(mrid)) + def get_identifiable(self, mrid: str) -> GrpcResult[Identifiable | None]: + return get_event_loop().run_until_complete(super()._get_identifiable(mrid)) - def get_identified_objects(self, mrids: Iterable[str]) -> GrpcResult[MultiObjectResult]: - return get_event_loop().run_until_complete(super()._get_identified_objects(mrids)) + def get_identifiables(self, mrids: Iterable[str]) -> GrpcResult[MultiObjectResult]: + return get_event_loop().run_until_complete(super()._get_identifiables(mrids)) def get_diagram_objects(self, mrid: Union[str, Iterable[str]]) -> GrpcResult[MultiObjectResult]: return get_event_loop().run_until_complete(super()._get_diagram_objects(mrid)) diff --git a/src/zepben/ewb/streaming/get/network_consumer.py b/src/zepben/ewb/streaming/get/network_consumer.py index 63b526e23..2a281e596 100644 --- a/src/zepben/ewb/streaming/get/network_consumer.py +++ b/src/zepben/ewb/streaming/get/network_consumer.py @@ -10,16 +10,16 @@ import warnings from asyncio import get_event_loop from itertools import chain -from typing import Iterable, Dict, Optional, AsyncGenerator, Union, List, Callable, Set, Tuple, Generic, TypeVar, Awaitable, cast, overload +from typing import Iterable, Dict, Optional, AsyncGenerator, Union, List, Callable, Set, Tuple, TypeVar, Awaitable, cast, overload, Type from zepben.protobuf.metadata.metadata_requests_pb2 import GetMetadataRequest from zepben.protobuf.metadata.metadata_responses_pb2 import GetMetadataResponse from zepben.protobuf.nc.nc_pb2_grpc import NetworkConsumerStub -from zepben.protobuf.nc.nc_requests_pb2 import GetIdentifiedObjectsRequest, GetNetworkHierarchyRequest, GetEquipmentForContainersRequest, \ +from zepben.protobuf.nc.nc_requests_pb2 import GetIdentifiablesRequest, GetNetworkHierarchyRequest, GetEquipmentForContainersRequest, \ GetEquipmentForRestrictionRequest, GetTerminalsForNodeRequest, IncludedEnergizingContainers as PBIncludedEnergizingContainers, \ IncludedEnergizedContainers as PBIncludedEnergizedContainers, NetworkState as PBNetworkState -from zepben.ewb import NetworkService, IdentifiedObject, Organisation, Location, OperationalRestriction, BaseVoltage, ConnectivityNode, Substation, Terminal, \ +from zepben.ewb import NetworkService, Organisation, Location, OperationalRestriction, BaseVoltage, ConnectivityNode, Substation, Terminal, \ AcLineSegment, Breaker, Disconnector, EnergyConsumer, \ EnergySource, EnergySourcePhase, \ Fuse, Jumper, PowerTransformer, Recloser, Circuit, \ @@ -31,6 +31,7 @@ ProtectionRelaySystem, GroundDisconnector, Ground, SeriesCompensator, PotentialTransformerInfo, PanDemandResponseFunction, BatteryControl, \ StaticVarCompensator, PerLengthPhaseImpedance, GroundingImpedance, PetersenCoil, ReactiveCapabilityCurve, SynchronousMachine, PowerSystemResource, Asset from zepben.ewb.dataclassy import dataclass +from zepben.ewb.model.cim.iec61970.base.core.identifiable import Identifiable from zepben.ewb.model.cim.extensions.iec61970.base.core.hv_customer import HvCustomer from zepben.ewb.model.cim.extensions.iec61970.base.core.site import Site from zepben.ewb.model.cim.extensions.iec61970.base.feeder.lv_substation import LvSubstation @@ -103,7 +104,7 @@ def generate_config(self): _map_network_state = EnumMapper(NetworkState, PBNetworkState) -class NetworkConsumerClient(CimConsumerClient[NetworkService]): +class NetworkConsumerClient(CimConsumerClient[NetworkService, NetworkConsumerStub]): """ Consumer client for a :class:`NetworkService`. @@ -114,7 +115,7 @@ class NetworkConsumerClient(CimConsumerClient[NetworkService]): check for mRIDs that were not found or retrieved but not added to service (this should not be the case unless you are processing things concurrently). """ - CIM_IO = TypeVar('CIM_IO', bound=IdentifiedObject) + CIM_IO = TypeVar('CIM_IO', bound=Identifiable) PB_IO = TypeVar('PB_IO') __service: NetworkService @@ -124,21 +125,19 @@ class NetworkConsumerClient(CimConsumerClient[NetworkService]): def service(self) -> NetworkService: return self.__service - _stub: NetworkConsumerStub = None - def __init__(self, channel=None, stub: NetworkConsumerStub = None, error_handlers: List[Callable[[Exception], bool]] = None, timeout: int = 60): """ :param channel: a gRPC channel used to create a stub if no stub is provided. :param stub: the gRPC stub to use for this consumer client. :param error_handlers: a collection of handlers to be processed for any errors that occur. """ - super().__init__(error_handlers=error_handlers, timeout=timeout) - if channel is None and stub is None: - raise ValueError("Must provide either a channel or a stub") if stub is not None: - self._stub = stub + super().__init__(error_handlers=error_handlers, timeout=timeout, stub=stub) + elif channel is not None: + super().__init__(error_handlers=error_handlers, timeout=timeout, stub=NetworkConsumerStub(channel)) else: - self._stub = NetworkConsumerStub(channel) + raise ValueError("Must provide either a channel or a stub") + self.__service = NetworkService() self.__network_hierarchy = None @@ -489,8 +488,8 @@ async def _process_equipment_for_container( self, it: Union[str, EquipmentContainer], include_energizing_containers: IncludedEnergizingContainers, include_energized_containers: IncludedEnergizedContainers, - network_state: NetworkState = NetworkState.NORMAL, - ) -> AsyncGenerator[IdentifiedObject, None]: + network_state: NetworkState = NetworkState.NORMAL + ) -> AsyncGenerator[Identifiable, None]: async for response in self._process_equipment_for_containers( [it.mrid if isinstance(it, EquipmentContainer) else it], include_energizing_containers, @@ -504,45 +503,45 @@ async def _process_equipment_for_containers( mrids: Iterable[str], include_energizing_containers: IncludedEnergizingContainers, include_energized_containers: IncludedEnergizedContainers, - network_state: NetworkState = NetworkState.NORMAL, - ) -> AsyncGenerator[IdentifiedObject, None]: + network_state: NetworkState = NetworkState.NORMAL + ) -> AsyncGenerator[Identifiable, None]: request = GetEquipmentForContainersRequest() request.includeEnergizingContainers = _map_include_energizing_containers.to_pb(include_energizing_containers) request.includeEnergizedContainers = _map_include_energized_containers.to_pb(include_energized_containers) request.networkState = _map_network_state.to_pb(network_state) responses = self._stub.getEquipmentForContainers(self._batch_send(request, mrids), timeout=self.timeout) async for response in responses: - for nio in response.identifiedObjects: - yield self._extract_identified_object("network", nio, _nio_type_to_cim) + for nio in response.identifiables: + yield self._extract_identifiable("network", nio, _nio_type_to_cim) async def _process_equipment_for_restriction( self, - it: Union[str, OperationalRestriction], - ) -> AsyncGenerator[IdentifiedObject, None]: + it: str | OperationalRestriction + ) -> AsyncGenerator[Identifiable, None]: mrid = it.mrid if isinstance(it, OperationalRestriction) else it responses = self._stub.getEquipmentForRestriction(GetEquipmentForRestrictionRequest(mrid=mrid), timeout=self.timeout) async for response in responses: - for nio in response.identifiedObjects: - yield self._extract_identified_object("network", nio, _nio_type_to_cim) + for nio in response.identifiables: + yield self._extract_identifiable("network", nio, _nio_type_to_cim) async def _process_terminals_for_connectivity_node( self, - it: Union[str, ConnectivityNode], - ) -> AsyncGenerator[IdentifiedObject, None]: + it: str | ConnectivityNode + ) -> AsyncGenerator[Identifiable, None]: mrid = it.mrid if isinstance(it, ConnectivityNode) else it responses = self._stub.getTerminalsForNode(GetTerminalsForNodeRequest(mrid=mrid), timeout=self.timeout) async for response in responses: # noinspection PyUnresolvedReferences yield self.service.get(response.terminal.mrid(), Terminal, default=None) or self.service.add_from_pb(response.terminal), response.terminal.mrid() - async def _process_identified_objects(self, mrids: Iterable[str]) -> AsyncGenerator[Tuple[Optional[IdentifiedObject], str], None]: + async def _process_identifiables(self, mrids: Iterable[str]) -> AsyncGenerator[Tuple[Identifiable | None, str], None]: if not mrids: return - responses = self._stub.getIdentifiedObjects(self._batch_send(GetIdentifiedObjectsRequest(), mrids), timeout=self.timeout) + responses = self._stub.getIdentifiables(self._batch_send(GetIdentifiablesRequest(), mrids), timeout=self.timeout) async for response in responses: - for nio in response.identifiedObjects: - yield self._extract_identified_object("network", nio, _nio_type_to_cim) + for nio in response.identifiables: + yield self._extract_identifiable("network", nio, _nio_type_to_cim) async def _handle_network_hierarchy(self, config: GetNetworkHierarchyConfig): response = await self._stub.getNetworkHierarchy(GetNetworkHierarchyRequest(**config.generate_config()), timeout=self.timeout) @@ -561,7 +560,7 @@ async def _handle_network_hierarchy(self, config: GetNetworkHierarchyConfig): return self.__network_hierarchy - async def _handle_multi_object_rpc(self, processor: Callable[[], AsyncGenerator[IdentifiedObject, None]]) -> GrpcResult[MultiObjectResult]: + async def _handle_multi_object_rpc(self, processor: Callable[[], AsyncGenerator[Identifiable, None]]) -> GrpcResult[MultiObjectResult]: result = MultiObjectResult() async def rpc(): @@ -588,7 +587,7 @@ async def _get_with_references( io = self.service.get(mrid, default=None) if not io: - response = await self._get_identified_object(mrid) + response = await self._get_identifiable(mrid) if response.was_failure: # noinspection PyArgumentList return GrpcResult(response.thrown, response.was_error_handled) @@ -633,7 +632,7 @@ async def _resolve_references(self, mor: MultiObjectResult) -> Optional[GrpcResu continue to_resolve.add(ref.to_mrid) - response = await self._get_identified_objects(to_resolve) + response = await self._get_identifiables(to_resolve) if response.was_failure: # noinspection PyArgumentList return GrpcResult(response.thrown, response.was_error_handled) @@ -661,11 +660,11 @@ def _to_map(self, objects: Iterable[PB_IO], class_: type[Generic[CIM_IO]]) -> Di class SyncNetworkConsumerClient(NetworkConsumerClient): """Synchronised wrapper for :class:`NetworkConsumerClient`""" - def get_identified_object(self, mrid: str) -> GrpcResult[IdentifiedObject]: - return get_event_loop().run_until_complete(super().get_identified_object(mrid)) + def get_identifiable(self, mrid: str) -> GrpcResult[Identifiable]: + return get_event_loop().run_until_complete(super().get_identifiable(mrid)) - def get_identified_objects(self, mrids: Iterable[str]) -> GrpcResult[MultiObjectResult]: - return get_event_loop().run_until_complete(super().get_identified_objects(mrids)) + def get_identifiables(self, mrids: Iterable[str]) -> GrpcResult[MultiObjectResult]: + return get_event_loop().run_until_complete(super().get_identifiables(mrids)) def get_equipment_for_container( self, diff --git a/src/zepben/ewb/streaming/get/query_network_state_client.py b/src/zepben/ewb/streaming/get/query_network_state_client.py index 0e5f883ab..65cd4afe7 100644 --- a/src/zepben/ewb/streaming/get/query_network_state_client.py +++ b/src/zepben/ewb/streaming/get/query_network_state_client.py @@ -18,22 +18,19 @@ from zepben.ewb.util import datetime_to_timestamp -class QueryNetworkStateClient(GrpcClient): +class QueryNetworkStateClient(GrpcClient[QueryNetworkStateServiceStub]): """ A client class that provides functionality to interact with the gRPC service for querying network states. A gRPC channel or stub must be provided. """ - _stub: QueryNetworkStateServiceStub = None - def __init__(self, channel=None, stub: QueryNetworkStateServiceStub = None, error_handlers: List[Callable[[Exception], bool]] = None, timeout: int = 60): - super().__init__(error_handlers=error_handlers, timeout=timeout) - if channel is None and stub is None: - raise ValueError("Must provide either a channel or a stub") if stub is not None: - self._stub = stub + super().__init__(error_handlers=error_handlers, timeout=timeout, stub=stub) + elif channel is not None: + super().__init__(error_handlers=error_handlers, timeout=timeout, stub=QueryNetworkStateServiceStub(channel)) else: - self._stub = QueryNetworkStateServiceStub(channel) + raise ValueError("Must provide either a channel or a stub") async def get_current_states(self, query_id: int, from_datetime: datetime, to_datetime: datetime) -> AsyncGenerator[CurrentStateEventBatch, None]: """ diff --git a/src/zepben/ewb/streaming/grpc/grpc.py b/src/zepben/ewb/streaming/grpc/grpc.py index fe06cc242..c6af3428f 100644 --- a/src/zepben/ewb/streaming/grpc/grpc.py +++ b/src/zepben/ewb/streaming/grpc/grpc.py @@ -79,21 +79,26 @@ def throw_on_unhandled_error(self) -> GrpcResult[T]: raise self.result return self +TStub = TypeVar("TStub") @dataclass(init=False, slots=True) -class GrpcClient(object): +class GrpcClient(Generic[TStub]): error_handlers: List[Callable[[Exception], bool]] = [] timeout: int = 0 '''Timeout for client gRPC requests''' - def __init__(self, error_handlers: List[Callable[[Exception], bool]] = None, timeout: int = 60): + _stub: TStub = None + + def __init__(self, error_handlers: List[Callable[[Exception], bool]] = None, timeout: int = 60, *, stub: T): if error_handlers: self.error_handlers = error_handlers.copy() else: self.error_handlers = list() self.timeout = timeout + self._stub = stub + def try_handle_error(self, e: Exception) -> bool: for handler in self.error_handlers: if handler(e): diff --git a/src/zepben/ewb/streaming/mutations/update_network_state_client.py b/src/zepben/ewb/streaming/mutations/update_network_state_client.py index 3c2b5f85b..70a81b2e1 100644 --- a/src/zepben/ewb/streaming/mutations/update_network_state_client.py +++ b/src/zepben/ewb/streaming/mutations/update_network_state_client.py @@ -16,22 +16,19 @@ from zepben.ewb.streaming.grpc.grpc import GrpcClient -class UpdateNetworkStateClient(GrpcClient): +class UpdateNetworkStateClient(GrpcClient[UpdateNetworkStateServiceStub]): """ A client class that provides functionality to interact with the gRPC service for updating network states. A gRPC channel or stub must be provided. """ - _stub: UpdateNetworkStateServiceStub = None - def __init__(self, channel=None, stub: UpdateNetworkStateServiceStub = None, error_handlers: List[Callable[[Exception], bool]] = None, timeout: int = 60): - super().__init__(error_handlers=error_handlers, timeout=timeout) - if channel is None and stub is None: - raise ValueError("Must provide either a channel or a stub") if stub is not None: - self._stub = stub + super().__init__(error_handlers=error_handlers, timeout=timeout, stub=stub) + elif channel is not None: + super().__init__(error_handlers=error_handlers, timeout=timeout, stub=UpdateNetworkStateServiceStub(channel)) else: - self._stub = UpdateNetworkStateServiceStub(channel) + raise ValueError("Must provide either a channel or a stub") async def set_current_states(self, batch_id: int, batch: Iterable[CurrentStateEvent]) -> SetCurrentStatesStatus: """ diff --git a/src/zepben/ewb/util.py b/src/zepben/ewb/util.py index bc09ebf57..215b0a882 100644 --- a/src/zepben/ewb/util.py +++ b/src/zepben/ewb/util.py @@ -34,7 +34,7 @@ from google.protobuf.timestamp_pb2 import Timestamp as PBTimestamp if TYPE_CHECKING: - from zepben.ewb import IdentifiedObject, TIdentifiedObject + from zepben.ewb.model.cim.iec61970.base.core.identifiable import Identifiable, TIdentifiable T = TypeVar('T') @@ -54,13 +54,13 @@ def iter_but_not_str(obj: Any): return isinstance(obj, Iterable) and not isinstance(obj, (str, bytes, bytearray, dict)) -def get_by_mrid(collection: Optional[Iterable[TIdentifiedObject]], mrid: str) -> TIdentifiedObject: +def get_by_mrid(collection: Optional[Iterable[TIdentifiable]], mrid: str) -> TIdentifiable: """ - Get an `IdentifiedObject` from `collection` based on + Get an `Identifiable` from `collection` based on its mRID. `collection` The collection to operate on - `mrid` The mRID of the `IdentifiedObject` to lookup in the collection - Returns The `IdentifiedObject` + `mrid` The mRID of the `Identifiable` to lookup in the collection + Returns The `Identifiable` Raises `KeyError` if `mrid` was not found in the collection. """ if not collection: @@ -75,13 +75,13 @@ def get_by_mrid(collection: Optional[Iterable[TIdentifiedObject]], mrid: str) -> raise KeyError(mrid) -def contains_mrid(collection: Optional[Iterable[IdentifiedObject]], mrid: str) -> bool: +def contains_mrid(collection: Optional[Iterable[Identifiable]], mrid: str) -> bool: """ - Check if a collection of `IdentifiedObject` contains an + Check if a collection of `Identifiable` contains an object with a specified mRID. `collection` The collection to operate on `mrid` The mRID to look up. - Returns True if an `IdentifiedObject` is found in the collection with the specified mRID, False otherwise. + Returns True if an `Identifiable` is found in the collection with the specified mRID, False otherwise. """ if not collection: return False @@ -94,7 +94,7 @@ def contains_mrid(collection: Optional[Iterable[IdentifiedObject]], mrid: str) - def safe_remove(collection: Optional[List[T]], obj: T) -> Optional[List[T]]: """ - Remove an IdentifiedObject from a collection safely. + Remove an Identifiable from a collection safely. Raises `ValueError` if `obj` is not in the collection. Returns The collection if successfully removed or None if after removal the collection was empty. """ @@ -106,9 +106,9 @@ def safe_remove(collection: Optional[List[T]], obj: T) -> Optional[List[T]]: return collection if collection else None -def safe_remove_by_id(collection: Optional[Dict[str, IdentifiedObject]], obj: IdentifiedObject) -> {Optional[Dict[str, IdentifiedObject]]}: +def safe_remove_by_id(collection: Optional[Dict[str, Identifiable]], obj: Identifiable) -> dict[str, Identifiable] | None: """ - Remove an IdentifiedObject from a collection safely. + Remove an Identifiable from a collection safely. Raises `KeyError` if `obj` is not in the collection. Returns The collection if successfully removed or None if after removal the collection was empty. """ @@ -181,9 +181,11 @@ def generate_id() -> str: return str(UUID(bytes=os.urandom(16), version=4)) +CPT = TypeVar("CPT", bound=type) + class classproperty(property): - def __get__(self, cls, owner: T) -> T: - return classmethod(self.fget).__get__(None, owner)() + def __get__(self, __instance: Any, __owner: type | None = None) -> Any: + return classmethod(self.fget).__get__(None, __owner)() def datetime_to_timestamp(date_time: Optional[datetime]) -> Optional[PBTimestamp]: diff --git a/test/cim/iec61970/base/core/test_identified_object.py b/test/cim/iec61970/base/core/test_identified_object.py index eacab7482..681e7ed31 100644 --- a/test/cim/iec61970/base/core/test_identified_object.py +++ b/test/cim/iec61970/base/core/test_identified_object.py @@ -21,7 +21,7 @@ # # noinspection PyArgumentList -identified_object_args = ["test_mrid", "test_name", "test_description", [Name("1", NameType("nt1"), Junction(mrid=generate_id()))]] +identified_object_args = ["test_mrid", "test_name", "test_description", [Name(name="1", type=NameType(name="nt1"), identified_object=Junction(mrid=generate_id()))]] def verify_identified_object_constructor_default(io: IdentifiedObject): @@ -59,7 +59,7 @@ def verify_identified_object_constructor_args(io: IdentifiedObject): def test_user_can_add_names_to_identified_object(): identified_object = IdentifiedObject(mrid=generate_id()) # noinspection PyArgumentList - name_type = NameType("type") + name_type = NameType(name="type") assert identified_object.num_names() == 0 identified_object.add_name(name_type, "1") @@ -112,9 +112,9 @@ def test_get_names_obtains_all_names_of_a_identified_object_with_a_given_name_ty identified_object, name_type = _create_multiple_base_names() # noinspection PyArgumentList - name_type2 = NameType("type2") + name_type2 = NameType(name="type2") # noinspection PyArgumentList - name_type3 = NameType("type3") + name_type3 = NameType(name="type3") identified_object.add_name(name_type2, "1") assert len(identified_object.get_names(name_type)) == 3 @@ -158,7 +158,7 @@ def test_clear_names_removes_all_names_from_the_identified_object_and_the_name_t def test_user_can_add_the_same_name_back_after_it_has_been_removed(): identified_object = IdentifiedObject(mrid=generate_id()) # noinspection PyArgumentList - name_type = NameType("type") + name_type = NameType(name="type") identified_object.add_name(name_type, "1") name1 = identified_object.get_name("type", "1") @@ -172,7 +172,7 @@ def test_user_can_add_the_same_name_back_after_it_has_been_removed(): def test_removing_name_from_empty_name_list_does_not_cause_any_issue(): identified_object = IdentifiedObject(mrid=generate_id()) # noinspection PyArgumentList - name_type = NameType("type") + name_type = NameType(name="type") identified_object.add_name(name_type, "1") name1 = identified_object.get_name("type", "1") @@ -190,7 +190,7 @@ def test_removing_name_from_empty_name_list_does_not_cause_any_issue(): def _create_multiple_base_names() -> Tuple[IdentifiedObject, NameType]: identified_object = IdentifiedObject(mrid=generate_id()) # noinspection PyArgumentList - name_type = NameType("type") + name_type = NameType(name="type") identified_object.add_name(name_type, "1") identified_object.add_name(name_type, "2") diff --git a/test/cim/iec61970/base/core/test_name.py b/test/cim/iec61970/base/core/test_name.py index 078de2d85..644f85c19 100644 --- a/test/cim/iec61970/base/core/test_name.py +++ b/test/cim/iec61970/base/core/test_name.py @@ -12,7 +12,7 @@ from zepben.ewb.model.cim.iec61970.base.wires.junction import Junction # noinspection PyArgumentList -name_args = ["1", NameType("nt1"), Junction(mrid=generate_id())] +name_args = ["1", NameType(name="nt1"), Junction(mrid=generate_id())] # @@ -26,18 +26,9 @@ def test_name_constructor_kwargs(name, type, identified_object, **kwargs): assert not kwargs, f"found unexpected args: {kwargs}" - n = Name(name, type, identified_object) + n = Name(name=name, type=type, identified_object=identified_object) assert n.name == name assert n.type == type assert n.identified_object == identified_object - - -def test_name_constructor_args(): - n = Name(*name_args) - - assert name_args == [ - n.name, - n.type, - n.identified_object - ] + assert n.mrid == f"{n.name}-{n.type.name}-{n.identified_object.mrid}" diff --git a/test/cim/iec61970/base/core/test_name_type.py b/test/cim/iec61970/base/core/test_name_type.py index ef7340b46..2fdbbd4ca 100644 --- a/test/cim/iec61970/base/core/test_name_type.py +++ b/test/cim/iec61970/base/core/test_name_type.py @@ -17,7 +17,7 @@ def test_name_type_constructor_default(): # noinspection PyArgumentList - nt = NameType("nt") + nt = NameType(name="nt") assert nt.name == "nt" assert nt.description is None @@ -36,16 +36,6 @@ def test_name_type_constructor_kwargs(name, description, **kwargs): assert not list(nt.names) -def test_name_type_constructor_args(): - # noinspection PyArgumentList - nt = NameType(*name_type_args) - - assert name_type_args == [ - nt.name, - nt.description - ] - assert not list(nt.names) - # # NOTE: The names collection is non-standard and can't be tested with the verify_container methods. @@ -54,7 +44,7 @@ def test_name_type_constructor_args(): def test_get_or_add_names(): # noinspection PyArgumentList - nt = NameType("nt") + nt = NameType(name="nt") j1 = Junction(mrid=generate_id()) j2 = Junction(mrid=generate_id()) @@ -72,7 +62,7 @@ def test_get_or_add_names(): def test_names(): # noinspection PyArgumentList - nt = NameType("nt") + nt = NameType(name="nt") j1 = Junction(mrid=generate_id()) j2 = Junction(mrid=generate_id()) @@ -86,7 +76,7 @@ def test_names(): def test_get_names(): # noinspection PyArgumentList - nt = NameType("nt") + nt = NameType(name="nt") j1 = Junction(mrid=generate_id()) j2 = Junction(mrid=generate_id()) @@ -103,7 +93,7 @@ def test_get_names(): def test_removes_names(): # noinspection PyArgumentList - nt = NameType("nt") + nt = NameType(name="nt") j1 = Junction(mrid=generate_id()) j2 = Junction(mrid=generate_id()) @@ -123,7 +113,7 @@ def test_removes_names(): def test_remove_name(): # noinspection PyArgumentList - nt = NameType("nt") + nt = NameType(name="nt") j1 = Junction(mrid=generate_id()) j2 = Junction(mrid=generate_id()) @@ -146,7 +136,7 @@ def test_remove_name(): def test_clear_names(): # noinspection PyArgumentList - nt = NameType("nt") + nt = NameType(name="nt") j1 = Junction(mrid=generate_id()) diff --git a/test/services/common/service_comparator_validator.py b/test/services/common/service_comparator_validator.py index 1424dbe6c..09ec01c5d 100644 --- a/test/services/common/service_comparator_validator.py +++ b/test/services/common/service_comparator_validator.py @@ -5,24 +5,32 @@ from types import MemberDescriptorType from typing import Optional, Any, Callable, TypeVar, Union, Type, Set, Generic, TypeAlias +from __future__ import annotations + +__all__ = ["ServiceComparatorValidator"] + +from typing import Optional, Any, Callable, TypeVar, Union, Set, Protocol, Generic, overload, Type, Concatenate + from zepben.ewb import (IdentifiedObject, TIdentifiedObject, ObjectDifference, BaseService, CollectionDifference, - Difference, ReferenceDifference, ValueDifference, IndexedDifference) + Difference, ReferenceDifference, ValueDifference, IndexedDifference, TIdentifiable, BaseServiceComparator, Identifiable) from zepben.ewb.model.cim.iec61970.base.core.name_type import NameType from zepben.ewb.services.network.network_service_comparator import NetworkServiceComparatorOptions TService = TypeVar("TService", bound=BaseService) +TOtherCreator = Callable[[str], Any] C = TypeVar("C") +T = TypeVar("T") R = TypeVar("R") -Property = Union[MemberDescriptorType, property] +TCreator = Union[Callable[[str], T], T] -TAddr: TypeAlias = Callable[[TIdentifiedObject, R], TIdentifiedObject] | Callable[[R], TIdentifiedObject] +TAddr: TypeAlias = Callable[[TIdentifiable, R], TIdentifiable] | Callable[[R], TIdentifiable] # -# NOTE: Should be using the following with TCreator[TIdentifiedObject] in the functions, but Python -# 3.10 and PyCharm don't like passing TCreator[TIdentifiedObject] to bind correctly, so until +# NOTE: Should be using the following with TCreator[TIdentifiable] in the functions, but Python +# 3.10 and PyCharm don't like passing TCreator[TIdentifiable] to bind correctly, so until # we move on to a new version of Python, it looks like we are stuck repeating ourselves. # -# TCreator: TypeAlias = Type[TIdentifiedObject] | Callable[[str], TIdentifiedObject] +# TCreator: TypeAlias = Type[TIdentifiable] | Callable[[str], TIdentifiable] # @@ -91,9 +99,9 @@ def validate_compare( def validate_property( self, prop: Property | R, # Isn't actually R, but that is what the type checker thinks when passing class member references. - creator: Type[TIdentifiedObject] | Callable[[str], TIdentifiedObject], # Update to TCreator[TIdentifiedObject] when available. - create_value: Callable[[TIdentifiedObject], R], - create_other_value: Callable[[TIdentifiedObject], R], + creator: Type[TIdentifiable] | Callable[[str], TIdentifiable], # Update to TCreator[TIdentifiable] when available. + create_value: Callable[[TIdentifiablet], R], + create_other_value: Callable[[TIdentifiable], R], options: NetworkServiceComparatorOptions = NetworkServiceComparatorOptions(), options_stop_compare: bool = False, expected_differences: Set[str] = None @@ -117,9 +125,9 @@ def validate_property( def validate_val_property( self, prop: Property, - creator: Type[TIdentifiedObject] | Callable[[str], TIdentifiedObject], # Update to TCreator[TIdentifiedObject] when available. - change_state: Callable[[TIdentifiedObject, R], Any], - other_change_state: Callable[[TIdentifiedObject, R], Any], + creator: Type[TIdentifiable] | Callable[[str], TIdentifiable], # Update to TCreator[TIdentifiable] when available. + change_state: Callable[[TIdentifiable, R], Any], + other_change_state: Callable[[TIdentifiable, R], Any], options: NetworkServiceComparatorOptions = NetworkServiceComparatorOptions(), options_stop_compare: bool = False, expected_differences: Set[str] = None @@ -142,10 +150,10 @@ def validate_val_property( def validate_collection( self, prop: Property, - add_to_collection: TAddr[TIdentifiedObject, R], - creator: Type[TIdentifiedObject] | Callable[[str], TIdentifiedObject], # Update to TCreator[TIdentifiedObject] when available. - create_item: Callable[[TIdentifiedObject], R], - create_other_item: Callable[[TIdentifiedObject], R], + add_to_collection: TAddr[TIdentifiable, R], + creator: Type[TIdentifiable] | Callable[[str], TIdentifiable], # Update to TCreator[TIdentifiable] when available. + create_item: Callable[[TIdentifiable], R], + create_other_item: Callable[[TIdentifiable], R], options: NetworkServiceComparatorOptions = NetworkServiceComparatorOptions(), options_stop_compare: bool = False, expected_differences: Set[str] = None @@ -183,7 +191,7 @@ def validate_collection( def validate_name_collection( self, - creator: Type[TIdentifiedObject] | Callable[[str], TIdentifiedObject], # Update to TCreator[TIdentifiedObject] when available. + creator: Type[TIdentifiable] | Callable[[str], TIdentifiable], # Update to TCreator[TIdentifiable] when available. options: NetworkServiceComparatorOptions = NetworkServiceComparatorOptions(), options_stop_compare: bool = False, expected_differences: Set[str] = None @@ -195,7 +203,7 @@ def validate_name_collection( in_target_difference = creator("mRID") # noinspection PyArgumentList - name_type = NameType("type") + name_type = NameType(name="type") in_source.add_name(name_type, "name1") in_target.add_name(name_type, "name1") @@ -226,9 +234,9 @@ def validate_indexed_collection( self, prop: Property, add_to_collection: TAddr, - creator: Type[TIdentifiedObject] | Callable[[str], TIdentifiedObject], # Update to TCreator[TIdentifiedObject] when available. - create_item: Callable[[TIdentifiedObject], R], - create_other_item: Callable[[TIdentifiedObject], R], + creator: Type[TIdentifiable] | Callable[[str], TIdentifiable], # Update to TCreator[TIdentifiable] when available. + create_item: Callable[[TIdentifiable], R], + create_other_item: Callable[[TIdentifiable], R], options: NetworkServiceComparatorOptions = NetworkServiceComparatorOptions(), options_stop_compare: bool = False, expected_differences: Set[str] = None @@ -270,14 +278,11 @@ def get_item(obj) -> Optional[R]: }) self._validate_expected(diff, options, options_stop_compare, expected_differences=expected_differences) - K = TypeVar('K') - R = TypeVar('R') - def validate_unordered_collection( self, prop: Property, add_to_collection: TAddr, - creator: Type[TIdentifiedObject] | Callable[[str], TIdentifiedObject], # Update to TCreator[TIdentifiedObject] when available. + creator: Type[TIdentifiable] | Callable[[str], TIdentifiable], # Update to TCreator[TIdentifiable] when available. create_item_1: Callable[[K], R], create_item_2: Callable[[K], R], create_diff_item_1: Callable[[K], R], @@ -354,7 +359,7 @@ def get_item_2(it): return source_empty @staticmethod - def _get_value_or_reference_difference(source: Optional[R], target: Optional[R]) -> Difference: + def _get_value_or_reference_difference(source: R | None, target: R | None) -> Difference: if isinstance(source, IdentifiedObject) or isinstance(target, IdentifiedObject): return ReferenceDifference(source, target) else: @@ -366,21 +371,21 @@ def _validate_expected(self, diff: ObjectDifference, options: NetworkServiceComp expected_differences=expected_differences) -def _prop_name(prop: Property) -> str: +def _prop_name(prop: property) -> str: if isinstance(prop, property): return prop.fget.__name__ else: return prop.__name__ -def _get_prop(it: Any, prop: Property) -> Any: +def _get_prop(it: Any, prop: property) -> Any: if isinstance(prop, property): return getattr(it, prop.fget.__name__) else: return getattr(it, prop.__name__) -def _set_prop(it: Any, prop: Property, value: Any): +def _set_prop(it: Any, prop: property, value: Any): if isinstance(prop, property): return setattr(it, prop.fset.__name__, value) else: diff --git a/test/services/common/test_base_service.py b/test/services/common/test_base_service.py index 163e453bc..b4f1ee990 100644 --- a/test/services/common/test_base_service.py +++ b/test/services/common/test_base_service.py @@ -241,6 +241,12 @@ def test_contains(service: BaseService): assert breaker.mrid in service assert service.get(breaker.mrid) + assert breaker in service + + not_added_breaker = Breaker(mrid=generate_id()) + + assert not_added_breaker not in service + assert "unknown" not in service with pytest.raises(KeyError): service.get("unknown") diff --git a/test/services/common/test_base_service_comparator.py b/test/services/common/test_base_service_comparator.py index 9bb723b3c..0f5edfbbe 100644 --- a/test/services/common/test_base_service_comparator.py +++ b/test/services/common/test_base_service_comparator.py @@ -158,6 +158,6 @@ def test_unordered_list_comparison_with_objects(): def _create_name_type(name_type: str, desc: str, name: str, io_mrid: str) -> NameType: # noinspection PyArgumentList - nt = NameType(name_type, desc) + nt = NameType(name=name_type, description=desc) nt.get_or_add_name(name, Junction(mrid=io_mrid)) return nt diff --git a/test/services/customer/translator/test_customer_translator.py b/test/services/customer/translator/test_customer_translator.py index 4887362bb..2a0141a59 100644 --- a/test/services/customer/translator/test_customer_translator.py +++ b/test/services/customer/translator/test_customer_translator.py @@ -53,7 +53,7 @@ def test_customer_service_translations(): def test_creates_new_name_type(): # noinspection PyArgumentList, PyUnresolvedReferences - pb = NameType("nt1 name", "nt1 desc").to_pb() + pb = NameType(name="nt1 name", description="nt1 desc").to_pb() # noinspection PyUnresolvedReferences cim = CustomerService().add_from_pb(pb) @@ -63,10 +63,10 @@ def test_creates_new_name_type(): def test_updates_existing_name_type(): # noinspection PyArgumentList, PyUnresolvedReferences - pb = NameType("nt1 name", "nt1 desc").to_pb() + pb = NameType(name="nt1 name", description="nt1 desc").to_pb() # noinspection PyArgumentList - nt = NameType("nt1 name") + nt = NameType(name="nt1 name") cs = CustomerService() cs.add_name_type(nt) # noinspection PyUnresolvedReferences diff --git a/test/services/diagram/translator/test_diagram_translator.py b/test/services/diagram/translator/test_diagram_translator.py index 0401967cf..de8c7223e 100644 --- a/test/services/diagram/translator/test_diagram_translator.py +++ b/test/services/diagram/translator/test_diagram_translator.py @@ -43,7 +43,7 @@ def test_diagram_service_translations(): def test_creates_new_name_type(): # noinspection PyArgumentList, PyUnresolvedReferences - pb = NameType("nt1 name", "nt1 desc").to_pb() + pb = NameType(name="nt1 name", description="nt1 desc").to_pb() # noinspection PyUnresolvedReferences cim = DiagramService().add_from_pb(pb) @@ -54,10 +54,10 @@ def test_creates_new_name_type(): def test_updates_existing_name_type(): # noinspection PyArgumentList, PyUnresolvedReferences - pb = NameType("nt1 name", "nt1 desc").to_pb() + pb = NameType(name="nt1 name", description="nt1 desc").to_pb() # noinspection PyArgumentList - nt = NameType("nt1 name") + nt = NameType(name="nt1 name") ds = DiagramService() ds.add_name_type(nt) # noinspection PyUnresolvedReferences diff --git a/test/services/network/translator/test_network_translator.py b/test/services/network/translator/test_network_translator.py index f28f7c8fe..e0c4b2954 100644 --- a/test/services/network/translator/test_network_translator.py +++ b/test/services/network/translator/test_network_translator.py @@ -277,7 +277,7 @@ def test_network_service_translations(): # NOTE: NameType is not sent via any grpc messages at this stage, so test it separately def test_creates_new_name_type(): # noinspection PyArgumentList, PyUnresolvedReferences - pb = NameType("nt1 name", "nt1 desc").to_pb() + pb = NameType(name="nt1 name", description="nt1 desc").to_pb() # noinspection PyUnresolvedReferences cim = NetworkService().add_from_pb(pb) @@ -288,10 +288,10 @@ def test_creates_new_name_type(): def test_updates_existing_name_type(): # noinspection PyArgumentList, PyUnresolvedReferences - pb = NameType("nt1 name", "nt1 desc").to_pb() + pb = NameType(name="nt1 name", description="nt1 desc").to_pb() # noinspection PyArgumentList - nt = NameType("nt1 name") + nt = NameType(name="nt1 name") ns = NetworkService() ns.add_name_type(nt) # noinspection PyUnresolvedReferences diff --git a/test/streaming/get/mock_server.py b/test/streaming/get/mock_server.py index 93ec14d24..168ea7e51 100644 --- a/test/streaming/get/mock_server.py +++ b/test/streaming/get/mock_server.py @@ -116,9 +116,9 @@ async def validate(self, client_test: Callable[[], Awaitable[None]], interaction # client test, as the pytest logging only give the outcome, not which line actually caused it. try: await client_test() - except Exception as ex: + except Exception as e: print(traceback.format_exc()) - raise ex + raise e # Wait for the server to finish. If this times out your test, it indicates that not all expected requests were received, or the request stream # wasn't closed/completed. diff --git a/test/streaming/get/pb_creators.py b/test/streaming/get/pb_creators.py index 35402cb11..c04c89400 100644 --- a/test/streaming/get/pb_creators.py +++ b/test/streaming/get/pb_creators.py @@ -5,6 +5,9 @@ __all__ = ['network_identified_objects', 'customer_identified_objects', 'diagram_identified_objects'] +from zepben.protobuf.cc.cc_data_pb2 import CustomerIdentifiable +from zepben.protobuf.dc.dc_data_pb2 import DiagramIdentifiable +from zepben.protobuf.nc.nc_data_pb2 import NetworkIdentifiable from hypothesis.strategies import composite from zepben.protobuf.cc.cc_data_pb2 import CustomerIdentifiedObject from zepben.protobuf.dc.dc_data_pb2 import DiagramIdentifiedObject @@ -24,69 +27,70 @@ def network_identified_objects(draw): # Extensions IEC61968 Asset Info # ################################## - draw(create_relay_info().map(lambda it: NetworkIdentifiedObject(relayInfo=it.to_pb()))), + draw(create_relay_info().map(lambda it: NetworkIdentifiable(relayInfo=it.to_pb()))), + draw(create_relay_info().map(lambda it: NetworkIdentifiable(relayInfo=it.to_pb()))), ################################ # Extensions IEC61968 Metering # ################################ - draw(create_pan_demand_response_function().map(lambda it: NetworkIdentifiedObject(panDemandResponseFunction=it.to_pb()))), + draw(create_pan_demand_response_function().map(lambda it: NetworkIdentifiable(panDemandResponseFunction=it.to_pb()))), ################################# # Extensions IEC61970 Base Core # ################################# - draw(create_site().map(lambda it: NetworkIdentifiedObject(site=it.to_pb()))), + draw(create_site().map(lambda it: NetworkIdentifiable(site=it.to_pb()))), ################################### # Extensions IEC61970 Base Feeder # ################################### - draw(create_loop().map(lambda it: NetworkIdentifiedObject(loop=it.to_pb()))), - draw(create_lv_feeder().map(lambda it: NetworkIdentifiedObject(lvFeeder=it.to_pb()))), + draw(create_loop().map(lambda it: NetworkIdentifiable(loop=it.to_pb()))), + draw(create_lv_feeder().map(lambda it: NetworkIdentifiable(lvFeeder=it.to_pb()))), ################################################## # Extensions IEC61970 Base Generation Production # ################################################## - draw(create_ev_charging_unit().map(lambda it: NetworkIdentifiedObject(evChargingUnit=it.to_pb()))), + draw(create_ev_charging_unit().map(lambda it: NetworkIdentifiable(evChargingUnit=it.to_pb()))), ####################################### # Extensions IEC61970 Base Protection # ####################################### - draw(create_distance_relay().map(lambda it: NetworkIdentifiedObject(distanceRelay=it.to_pb()))), - draw(create_protection_relay_scheme().map(lambda it: NetworkIdentifiedObject(protectionRelayScheme=it.to_pb()))), - draw(create_protection_relay_system().map(lambda it: NetworkIdentifiedObject(protectionRelaySystem=it.to_pb()))), - draw(create_voltage_relay().map(lambda it: NetworkIdentifiedObject(voltageRelay=it.to_pb()))), + draw(create_distance_relay().map(lambda it: NetworkIdentifiable(distanceRelay=it.to_pb()))), + draw(create_protection_relay_scheme().map(lambda it: NetworkIdentifiable(protectionRelayScheme=it.to_pb()))), + draw(create_protection_relay_system().map(lambda it: NetworkIdentifiable(protectionRelaySystem=it.to_pb()))), + draw(create_voltage_relay().map(lambda it: NetworkIdentifiable(voltageRelay=it.to_pb()))), ################################## # Extensions IEC61970 Base Wires # ################################## - draw(create_battery_control().map(lambda it: NetworkIdentifiedObject(batteryControl=it.to_pb()))), + draw(create_battery_control().map(lambda it: NetworkIdentifiable(batteryControl=it.to_pb()))), ####################### # IEC61968 Asset Info # ####################### - draw(create_cable_info().map(lambda it: NetworkIdentifiedObject(cableInfo=it.to_pb()))), - draw(create_no_load_test().map(lambda it: NetworkIdentifiedObject(noLoadTest=it.to_pb()))), - draw(create_open_circuit_test().map(lambda it: NetworkIdentifiedObject(openCircuitTest=it.to_pb()))), - draw(create_overhead_wire_info().map(lambda it: NetworkIdentifiedObject(overheadWireInfo=it.to_pb()))), - draw(create_power_transformer_info().map(lambda it: NetworkIdentifiedObject(powerTransformerInfo=it.to_pb()))), - draw(create_short_circuit_test().map(lambda it: NetworkIdentifiedObject(shortCircuitTest=it.to_pb()))), - draw(create_shunt_compensator_info().map(lambda it: NetworkIdentifiedObject(shuntCompensatorInfo=it.to_pb()))), - draw(create_switch_info().map(lambda it: NetworkIdentifiedObject(switchInfo=it.to_pb()))), - draw(create_transformer_end_info().map(lambda it: NetworkIdentifiedObject(transformerEndInfo=it.to_pb()))), - draw(create_transformer_tank_info().map(lambda it: NetworkIdentifiedObject(transformerTankInfo=it.to_pb()))), + draw(create_cable_info().map(lambda it: NetworkIdentifiable(cableInfo=it.to_pb()))), + draw(create_no_load_test().map(lambda it: NetworkIdentifiable(noLoadTest=it.to_pb()))), + draw(create_open_circuit_test().map(lambda it: NetworkIdentifiable(openCircuitTest=it.to_pb()))), + draw(create_overhead_wire_info().map(lambda it: NetworkIdentifiable(overheadWireInfo=it.to_pb()))), + draw(create_power_transformer_info().map(lambda it: NetworkIdentifiable(powerTransformerInfo=it.to_pb()))), + draw(create_short_circuit_test().map(lambda it: NetworkIdentifiable(shortCircuitTest=it.to_pb()))), + draw(create_shunt_compensator_info().map(lambda it: NetworkIdentifiable(shuntCompensatorInfo=it.to_pb()))), + draw(create_switch_info().map(lambda it: NetworkIdentifiable(switchInfo=it.to_pb()))), + draw(create_transformer_end_info().map(lambda it: NetworkIdentifiable(transformerEndInfo=it.to_pb()))), + draw(create_transformer_tank_info().map(lambda it: NetworkIdentifiable(transformerTankInfo=it.to_pb()))), ################### # IEC61968 Assets # ################### - draw(create_asset_owner().map(lambda it: NetworkIdentifiedObject(assetOwner=it.to_pb()))), - draw(create_streetlight().map(lambda it: NetworkIdentifiedObject(streetlight=it.to_pb()))), + draw(create_asset_owner().map(lambda it: NetworkIdentifiable(assetOwner=it.to_pb()))), + draw(create_streetlight().map(lambda it: NetworkIdentifiable(streetlight=it.to_pb()))), ################### # IEC61968 Common # diff --git a/test/streaming/get/test_consumer.py b/test/streaming/get/test_consumer.py index 8571f23c2..707d5186d 100644 --- a/test/streaming/get/test_consumer.py +++ b/test/streaming/get/test_consumer.py @@ -10,16 +10,16 @@ @pytest.mark.asyncio async def test_abstract_coverage(): - client = CimConsumerClient() + client = CimConsumerClient(stub='not-a-stub-but-required-because-kwarg') with pytest.raises(NotImplementedError): (await client.service).throw_on_error() with pytest.raises(NotImplementedError): - (await client.get_identified_object("id")).throw_on_error() + (await client.get_identifiable("id")).throw_on_error() with pytest.raises(NotImplementedError): - (await client.get_identified_objects(["id"])).throw_on_error() + (await client.get_identifiables(["id"])).throw_on_error() with pytest.raises(NotImplementedError): (await client.get_metadata()).throw_on_error() diff --git a/test/streaming/get/test_customer_consumer.py b/test/streaming/get/test_customer_consumer.py index 1edfff529..e392039cc 100644 --- a/test/streaming/get/test_customer_consumer.py +++ b/test/streaming/get/test_customer_consumer.py @@ -9,8 +9,8 @@ import pytest from hypothesis import given, settings, Phase from zepben.protobuf.cc import cc_pb2 -from zepben.protobuf.cc.cc_data_pb2 import CustomerIdentifiedObject -from zepben.protobuf.cc.cc_responses_pb2 import GetIdentifiedObjectsResponse, GetCustomersForContainerResponse +from zepben.protobuf.cc.cc_data_pb2 import CustomerIdentifiable +from zepben.protobuf.cc.cc_responses_pb2 import GetIdentifiablesResponse, GetCustomersForContainerResponse from cim.fill_fields import create_customer from streaming.get.data.metadata import create_metadata, create_metadata_response @@ -45,23 +45,23 @@ def test_constructor(self): async def test_get_identified_objects_supported_types(self, cios): requested = [] for cio in cios: - mrid = getattr(cio, cio.WhichOneof("identifiedObject"), None).mrid() + mrid = getattr(cio, cio.WhichOneof("identifiable"), None).mrid() requested.append(mrid) async def client_test(): - result = await self.client.get_identified_objects([mrid]) + result = await self.client.get_identifiables([mrid]) if not mrid: assert result.was_failure assert isinstance(result.thrown, KeyError) return mor = result.throw_on_error().value - assert mrid in self.service, f"type: {cio.WhichOneof('identifiedObject')} mrid: {mrid}" - assert mor.objects[mrid] is not None, f"type: {cio.WhichOneof('identifiedObject')} mrid: {mrid}" - assert self.service[mrid] is mor.objects[mrid], f"type: {cio.WhichOneof('identifiedObject')} mrid: {mrid}" + assert mrid in self.service, f"type: {cio.WhichOneof('identifiable')} mrid: {mrid}" + assert mor.objects[mrid] is not None, f"type: {cio.WhichOneof('identifiable')} mrid: {mrid}" + assert self.service[mrid] is mor.objects[mrid], f"type: {cio.WhichOneof('identifiable')} mrid: {mrid}" - response = GetIdentifiedObjectsResponse(identifiedObjects=[cio]) - await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiedObjects', stream_from_fixed([mrid], [response]))]) + response = GetIdentifiablesResponse(identifiables=[cio]) + await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiables', stream_from_fixed([mrid], [response]))]) assert len(requested) == len(cios) == self.service.len_of() @@ -70,29 +70,35 @@ async def test_get_identified_objects_not_found(self): mrid = "unknown" async def client_test(): - mor = (await self.client.get_identified_objects([mrid])).throw_on_error().value + mor = (await self.client.get_identifiables([mrid])).throw_on_error().value assert mor.objects == {} assert mor.failed == {mrid} - await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiedObjects', stream_from_fixed([mrid], []))]) + await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiables', stream_from_fixed([mrid], []))]) # noinspection PyUnresolvedReferences @pytest.mark.asyncio async def test_get_identified_objects_partial_not_found(self): async def client_test(): - mor = (await self.client.get_identified_objects(["customer1", "customer2", "customer3"])).throw_on_error().value + mor = (await self.client.get_identifiables(["customer1", "customer2", "customer3"])).throw_on_error().value assert mor.objects.keys() == {"customer1", "customer2"} assert mor.failed == {"customer3"} - response1 = GetIdentifiedObjectsResponse(identifiedObjects=[CustomerIdentifiedObject(customer=Customer(mrid="customer1").to_pb())]) - response2 = GetIdentifiedObjectsResponse(identifiedObjects=[CustomerIdentifiedObject(customer=Customer(mrid="customer2").to_pb())]) + response1 = GetIdentifiablesResponse(identifiables=[CustomerIdentifiable(customer=Customer(mrid="customer1").to_pb())]) + response2 = GetIdentifiablesResponse(identifiables=[CustomerIdentifiable(customer=Customer(mrid="customer2").to_pb())]) await self.mock_server.validate( client_test, [ - StreamGrpc('getIdentifiedObjects', stream_from_fixed(["customer1", "customer2", "customer3"], [response1, response2])), + StreamGrpc('getIdentifiables', stream_from_fixed(["customer1", "customer2", "customer3"], [response1, response2])), + ], + ) + await self.mock_server.validate( + client_test, + [ + StreamGrpc('getIdentifiables', stream_from_fixed(["customer1", "customer2", "customer3"], [response1, response2])), ], ) @@ -102,27 +108,27 @@ async def test_get_identified_object(self, customer_): mrid = customer_.mrid() async def client_test(): - io = (await self.client.get_identified_object(mrid)).throw_on_error().value + io = (await self.client.get_identifiable(mrid)).throw_on_error().value assert mrid in self.service assert io is not None assert self.service[mrid] is io - response = GetIdentifiedObjectsResponse(identifiedObjects=[CustomerIdentifiedObject(customer=customer_)]) - await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiedObjects', stream_from_fixed([mrid], [response]))]) + response = GetIdentifiablesResponse(identifiables=[CustomerIdentifiable(customer=customer_)]) + await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiables', stream_from_fixed([mrid], [response]))]) @pytest.mark.asyncio async def test_get_identified_object_not_found(self): mrid = "unknown" async def client_test(): - result = (await self.client.get_identified_object(mrid)) + result = (await self.client.get_identifiable(mrid)) assert result.was_failure assert isinstance(result.thrown, ValueError) assert str(result.thrown) == f"No object with mRID {mrid} could be found." - await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiedObjects', stream_from_fixed([mrid], []))]) + await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiables', stream_from_fixed([mrid], []))]) @pytest.mark.asyncio @given(create_customer().map(lambda it: it.to_pb())) @@ -139,7 +145,7 @@ async def client_test(): assert io is not None assert self.service[mrid] is io - response = GetCustomersForContainerResponse(identifiedObjects=[CustomerIdentifiedObject(customer=c)]) + response = GetCustomersForContainerResponse(identifiables=[CustomerIdentifiable(customer=c)]) await self.mock_server.validate(client_test, [StreamGrpc('getCustomersForContainer', stream_from_fixed([mrid], [response]))]) @pytest.mark.asyncio @@ -170,21 +176,21 @@ def _assert_contains_mrids(service: BaseService, *mrids): def _response_of(io: IdentifiedObject, response_type): - return response_type(identifiedObjects=[_to_customer_identified_object(io)]) + return response_type(identifiables=[_to_customer_identified_object(io)]) # noinspection PyUnresolvedReferences -def _to_customer_identified_object(obj) -> CustomerIdentifiedObject: +def _to_customer_identified_object(obj) -> CustomerIdentifiable: if isinstance(obj, Customer): - cio = CustomerIdentifiedObject(customer=obj.to_pb()) + cio = CustomerIdentifiable(customer=obj.to_pb()) elif isinstance(obj, CustomerAgreement): - cio = CustomerIdentifiedObject(customerAgreement=obj.to_pb()) + cio = CustomerIdentifiable(customerAgreement=obj.to_pb()) elif isinstance(obj, Organisation): - cio = CustomerIdentifiedObject(organisation=obj.to_pb()) + cio = CustomerIdentifiable(organisation=obj.to_pb()) elif isinstance(obj, PricingStructure): - cio = CustomerIdentifiedObject(pricingStructure=obj.to_pb()) + cio = CustomerIdentifiable(pricingStructure=obj.to_pb()) elif isinstance(obj, Tariff): - cio = CustomerIdentifiedObject(tariff=obj.to_pb()) + cio = CustomerIdentifiable(tariff=obj.to_pb()) else: raise Exception(f"Missing class in create response - you should implement it: {str(obj)}") return cio diff --git a/test/streaming/get/test_diagram_consumer.py b/test/streaming/get/test_diagram_consumer.py index 30ee6b472..3af1de0b5 100644 --- a/test/streaming/get/test_diagram_consumer.py +++ b/test/streaming/get/test_diagram_consumer.py @@ -9,8 +9,8 @@ import pytest from hypothesis import given, settings, Phase from zepben.protobuf.dc import dc_pb2 -from zepben.protobuf.dc.dc_data_pb2 import DiagramIdentifiedObject -from zepben.protobuf.dc.dc_responses_pb2 import GetIdentifiedObjectsResponse, GetDiagramObjectsResponse +from zepben.protobuf.dc.dc_data_pb2 import DiagramIdentifiable +from zepben.protobuf.dc.dc_responses_pb2 import GetIdentifiablesResponse, GetDiagramObjectsResponse from cim.fill_fields import create_diagram, create_diagram_object from streaming.get.data.metadata import create_metadata, create_metadata_response @@ -47,23 +47,23 @@ def test_constructor(self): async def test_get_identified_objects_supported_types(self, dios): requested = [] for dio in dios: - mrid = getattr(dio, dio.WhichOneof("identifiedObject"), None).mrid() + mrid = getattr(dio, dio.WhichOneof("identifiable"), None).mrid() requested.append(mrid) async def client_test(): - result = await self.client.get_identified_objects([mrid]) + result = await self.client.get_identifiables([mrid]) if not mrid: assert result.was_failure assert isinstance(result.thrown, KeyError) return mor = result.throw_on_error().value - assert mrid in self.service, f"type: {dio.WhichOneof('identifiedObject')} mrid: {mrid}" - assert mor.objects[mrid] is not None, f"type: {dio.WhichOneof('identifiedObject')} mrid: {mrid}" - assert self.service[mrid] is mor.objects[mrid], f"type: {dio.WhichOneof('identifiedObject')} mrid: {mrid}" + assert mrid in self.service, f"type: {dio.WhichOneof('identifiable')} mrid: {mrid}" + assert mor.objects[mrid] is not None, f"type: {dio.WhichOneof('identifiable')} mrid: {mrid}" + assert self.service[mrid] is mor.objects[mrid], f"type: {dio.WhichOneof('identifiable')} mrid: {mrid}" - response = GetIdentifiedObjectsResponse(identifiedObjects=[dio]) - await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiedObjects', stream_from_fixed([mrid], [response]))]) + response = GetIdentifiablesResponse(identifiables=[dio]) + await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiables', stream_from_fixed([mrid], [response]))]) assert len(requested) == len(dios) == self.service.len_of() @@ -72,29 +72,29 @@ async def test_get_identified_objects_not_found(self): mrid = "unknown" async def client_test(): - mor = (await self.client.get_identified_objects([mrid])).throw_on_error().value + mor = (await self.client.get_identifiables([mrid])).throw_on_error().value assert mor.objects == {} assert mor.failed == {mrid} - await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiedObjects', stream_from_fixed([mrid], []))]) + await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiables', stream_from_fixed([mrid], []))]) # noinspection PyUnresolvedReferences @pytest.mark.asyncio async def test_get_identified_objects_partial_not_found(self): async def client_test(): - mor = (await self.client.get_identified_objects(["diagram1", "diagram2", "diagram3"])).throw_on_error().value + mor = (await self.client.get_identifiables(["diagram1", "diagram2", "diagram3"])).throw_on_error().value assert mor.objects.keys() == {"diagram1", "diagram2"} assert mor.failed == {"diagram3"} - response1 = GetIdentifiedObjectsResponse(identifiedObjects=[DiagramIdentifiedObject(diagram=Diagram(mrid="diagram1").to_pb())]) - response2 = GetIdentifiedObjectsResponse(identifiedObjects=[DiagramIdentifiedObject(diagram=Diagram(mrid="diagram2").to_pb())]) + response1 = GetIdentifiablesResponse(identifiables=[DiagramIdentifiable(diagram=Diagram(mrid="diagram1").to_pb())]) + response2 = GetIdentifiablesResponse(identifiables=[DiagramIdentifiable(diagram=Diagram(mrid="diagram2").to_pb())]) await self.mock_server.validate( client_test, [ - StreamGrpc('getIdentifiedObjects', stream_from_fixed(["diagram1", "diagram2", "diagram3"], [response1, response2])), + StreamGrpc('getIdentifiables', stream_from_fixed(["diagram1", "diagram2", "diagram3"], [response1, response2])), ], ) @@ -104,27 +104,27 @@ async def test_get_identified_object(self, diagram_): mrid = diagram_.mrid() async def client_test(): - io = (await self.client.get_identified_object(mrid)).throw_on_error().value + io = (await self.client.get_identifiable(mrid)).throw_on_error().value assert mrid in self.service assert io is not None assert self.service[mrid] is io - response = GetIdentifiedObjectsResponse(identifiedObjects=[DiagramIdentifiedObject(diagram=diagram_)]) - await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiedObjects', stream_from_fixed([mrid], [response]))]) + response = GetIdentifiablesResponse(identifiables=[DiagramIdentifiable(diagram=diagram_)]) + await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiables', stream_from_fixed([mrid], [response]))]) @pytest.mark.asyncio async def test_get_identified_object_not_found(self): mrid = "unknown" async def client_test(): - result = (await self.client.get_identified_object(mrid)) + result = (await self.client.get_identifiable(mrid)) assert result.was_failure assert isinstance(result.thrown, ValueError) assert str(result.thrown) == f"No object with mRID {mrid} could be found." - await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiedObjects', stream_from_fixed([mrid], []))]) + await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiables', stream_from_fixed([mrid], []))]) @pytest.mark.asyncio @given(create_diagram_object().map(lambda it: it.to_pb())) @@ -141,7 +141,7 @@ async def client_test(): assert io is not None assert self.service[mrid] is io - response = GetDiagramObjectsResponse(identifiedObjects=[DiagramIdentifiedObject(diagramObject=dio)]) + response = GetDiagramObjectsResponse(identifiables=[DiagramIdentifiable(diagramObject=dio)]) await self.mock_server.validate(client_test, [StreamGrpc('getDiagramObjects', stream_from_fixed([mrid], [response]))]) @pytest.mark.asyncio @@ -172,15 +172,15 @@ def _assert_contains_mrids(service: BaseService, *mrids): def _response_of(io: IdentifiedObject, response_type): - return response_type(identifiedObjects=[_to_diagram_identified_object(io)]) + return response_type(identifiables=[_to_diagram_identified_object(io)]) # noinspection PyUnresolvedReferences -def _to_diagram_identified_object(obj) -> DiagramIdentifiedObject: +def _to_diagram_identified_object(obj) -> DiagramIdentifiable: if isinstance(obj, Diagram): - dio = DiagramIdentifiedObject(diagram=obj.to_pb()) + dio = DiagramIdentifiable(diagram=obj.to_pb()) elif isinstance(obj, DiagramObject): - dio = DiagramIdentifiedObject(diagramObject=obj.to_pb()) + dio = DiagramIdentifiable(diagramObject=obj.to_pb()) else: raise Exception(f"Missing class in create response - you should implement it: {str(obj)}") return dio diff --git a/test/streaming/get/test_network_consumer.py b/test/streaming/get/test_network_consumer.py index 8bb040cc7..9608acd72 100644 --- a/test/streaming/get/test_network_consumer.py +++ b/test/streaming/get/test_network_consumer.py @@ -10,12 +10,13 @@ import pytest from google.protobuf.any_pb2 import Any from hypothesis import given, settings, Phase +from typing_extensions import deprecated from zepben.protobuf.nc import nc_pb2 -from zepben.protobuf.nc.nc_data_pb2 import NetworkIdentifiedObject -from zepben.protobuf.nc.nc_requests_pb2 import GetIdentifiedObjectsRequest, GetEquipmentForContainersRequest, GetEquipmentForRestrictionRequest, \ +from zepben.protobuf.nc.nc_data_pb2 import NetworkIdentifiable +from zepben.protobuf.nc.nc_requests_pb2 import GetIdentifiablesRequest, GetEquipmentForContainersRequest, GetEquipmentForRestrictionRequest, \ GetTerminalsForNodeRequest, IncludedEnergizingContainers as PBIncludedEnergizingContainers, IncludedEnergizedContainers as PBIncludedEnergizedContainers, \ NetworkState as PBNetworkState -from zepben.protobuf.nc.nc_responses_pb2 import GetIdentifiedObjectsResponse, GetEquipmentForContainersResponse, \ +from zepben.protobuf.nc.nc_responses_pb2 import GetIdentifiablesResponse, GetEquipmentForContainersResponse, \ GetEquipmentForRestrictionResponse, GetTerminalsForNodeResponse, GetNetworkHierarchyResponse from cim.fill_fields import create_ac_line_segment @@ -66,18 +67,18 @@ def test_constructor(self): async def test_get_identified_objects_supported_types(self, nios): requested = [] for nio in nios: - mrid = getattr(nio, nio.WhichOneof("identifiedObject"), None).mrid() + mrid = getattr(nio, nio.WhichOneof("identifiable"), None).mrid() requested.append(mrid) async def client_test(): - mor = (await self.client.get_identified_objects([mrid])).throw_on_error().value + mor = (await self.client.get_identifiables([mrid])).throw_on_error().value - assert mrid in self.service, f"type: {nio.WhichOneof('identifiedObject')} mrid: {mrid}" - assert mor.objects[mrid] is not None, f"type: {nio.WhichOneof('identifiedObject')} mrid: {mrid}" - assert self.service[mrid] is mor.objects[mrid], f"type: {nio.WhichOneof('identifiedObject')} mrid: {mrid}" + assert mrid in self.service, f"type: {nio.WhichOneof('identifiable')} mrid: {mrid}" + assert mor.objects[mrid] is not None, f"type: {nio.WhichOneof('identifiable')} mrid: {mrid}" + assert self.service[mrid] is mor.objects[mrid], f"type: {nio.WhichOneof('identifiable')} mrid: {mrid}" - response = GetIdentifiedObjectsResponse(identifiedObjects=[nio]) - await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiedObjects', stream_from_fixed([mrid], [response]))]) + response = GetIdentifiablesResponse(identifiables=[nio]) + await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiables', stream_from_fixed([mrid], [response]))]) assert len(requested) == len(nios) == self.service.len_of() @@ -86,29 +87,29 @@ async def test_get_identified_objects_not_found(self): mrid = "unknown" async def client_test(): - mor = (await self.client.get_identified_objects([mrid])).throw_on_error().value + mor = (await self.client.get_identifiables([mrid])).throw_on_error().value assert mor.objects == {} assert mor.failed == {mrid} - await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiedObjects', stream_from_fixed([mrid], []))]) + await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiables', stream_from_fixed([mrid], []))]) # noinspection PyUnresolvedReferences @pytest.mark.asyncio async def test_get_identified_objects_partial_not_found(self): async def client_test(): - mor = (await self.client.get_identified_objects(["acls1", "acls2", "acls3"])).throw_on_error().value + mor = (await self.client.get_identifiables(["acls1", "acls2", "acls3"])).throw_on_error().value assert mor.objects.keys() == {"acls1", "acls2"} assert mor.failed == {"acls3"} - response1 = GetIdentifiedObjectsResponse(identifiedObjects=[NetworkIdentifiedObject(acLineSegment=AcLineSegment(mrid="acls1").to_pb())]) - response2 = GetIdentifiedObjectsResponse(identifiedObjects=[NetworkIdentifiedObject(acLineSegment=AcLineSegment(mrid="acls2").to_pb())]) + response1 = GetIdentifiablesResponse(identifiables=[NetworkIdentifiable(acLineSegment=AcLineSegment(mrid="acls1").to_pb())]) + response2 = GetIdentifiablesResponse(identifiables=[NetworkIdentifiable(acLineSegment=AcLineSegment(mrid="acls2").to_pb())]) await self.mock_server.validate( client_test, [ - StreamGrpc('getIdentifiedObjects', stream_from_fixed(["acls1", "acls2", "acls3"], [response1, response2])), + StreamGrpc('getIdentifiables', stream_from_fixed(["acls1", "acls2", "acls3"], [response1, response2])), ], ) @@ -118,27 +119,27 @@ async def test_get_identified_object(self, acls): mrid = acls.mrid() async def client_test(): - io = (await self.client.get_identified_object(mrid)).throw_on_error().value + io = (await self.client.get_identifiable(mrid)).throw_on_error().value assert mrid in self.service assert io is not None assert self.service[mrid] is io - response = GetIdentifiedObjectsResponse(identifiedObjects=[NetworkIdentifiedObject(acLineSegment=acls)]) - await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiedObjects', stream_from_fixed([mrid], [response]))]) + response = GetIdentifiablesResponse(identifiables=[NetworkIdentifiable(acLineSegment=acls)]) + await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiables', stream_from_fixed([mrid], [response]))]) @pytest.mark.asyncio async def test_get_identified_object_not_found(self): mrid = "unknown" async def client_test(): - result = (await self.client.get_identified_object(mrid)) + result = (await self.client.get_identifiable(mrid)) assert result.was_failure assert isinstance(result.thrown, ValueError) assert str(result.thrown) == f"No object with mRID {mrid} could be found." - await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiedObjects', stream_from_fixed([mrid], []))]) + await self.mock_server.validate(client_test, [StreamGrpc('getIdentifiables', stream_from_fixed([mrid], []))]) @pytest.mark.asyncio async def test_get_metadata(self): @@ -297,7 +298,7 @@ async def client_test(): interactions.extend( [ StreamGrpc('getEquipmentForContainers', [_create_container_equipment_responses(ns, containers)]), - StreamGrpc('getIdentifiedObjects', [_create_object_responses(ns, assoc_objs)]), + StreamGrpc('getIdentifiables', [_create_object_responses(ns, assoc_objs)]), ], ) @@ -328,8 +329,8 @@ async def client_test(): [ UnaryGrpc('getNetworkHierarchy', unary_from_fixed(None, _create_hierarchy_response(feeder_network))), StreamGrpc('getEquipmentForContainers', [_create_container_responses(feeder_network)]), - StreamGrpc('getIdentifiedObjects', [object_responses]), - StreamGrpc('getIdentifiedObjects', [object_responses]) + StreamGrpc('getIdentifiables', [object_responses]), + StreamGrpc('getIdentifiables', [object_responses]) ], ) @@ -371,10 +372,10 @@ async def client_test(): client_test, [ UnaryGrpc('getNetworkHierarchy', unary_from_fixed(None, _create_hierarchy_response(lv_feeders_with_open_point))), - StreamGrpc('getIdentifiedObjects', [object_responses]), + StreamGrpc('getIdentifiables', [object_responses]), StreamGrpc('getEquipmentForContainers', [_create_container_equipment_responses(lv_feeders_with_open_point)]), - StreamGrpc('getIdentifiedObjects', [object_responses]), - StreamGrpc('getIdentifiedObjects', [object_responses]), + StreamGrpc('getIdentifiables', [object_responses]), + StreamGrpc('getIdentifiables', [object_responses]), ], ) @@ -419,10 +420,10 @@ async def client_test(): client_test, [ UnaryGrpc('getNetworkHierarchy', unary_from_fixed(None, _create_hierarchy_response(lv_feeder_with_pole))), - StreamGrpc('getIdentifiedObjects', [object_responses]), + StreamGrpc('getIdentifiables', [object_responses]), StreamGrpc('getEquipmentForContainers', [_create_container_equipment_responses(lv_feeder_with_pole)]), - StreamGrpc('getIdentifiedObjects', [object_responses]), - StreamGrpc('getIdentifiedObjects', [object_responses]), + StreamGrpc('getIdentifiables', [object_responses]), + StreamGrpc('getIdentifiables', [object_responses]), ], ) @@ -468,8 +469,8 @@ async def client_test(): ) ], ), - StreamGrpc('getIdentifiedObjects', [object_responses]), - StreamGrpc('getIdentifiedObjects', [object_responses]) + StreamGrpc('getIdentifiables', [object_responses]), + StreamGrpc('getIdentifiables', [object_responses]) ], ) @@ -568,7 +569,7 @@ async def client_test(): 'getEquipmentForContainers', [_create_container_equipment_responses(ns, loop_containers, network_state=PBNetworkState.NETWORK_STATE_ALL)], ), - StreamGrpc('getIdentifiedObjects', [_create_object_responses(ns, assoc_objs)]) + StreamGrpc('getIdentifiables', [_create_object_responses(ns, assoc_objs)]) ], ) @@ -594,7 +595,7 @@ async def client_test(): 'getEquipmentForContainers', [_create_container_equipment_responses(ns, loop_containers, network_state=PBNetworkState.NETWORK_STATE_ALL)], ), - StreamGrpc('getIdentifiedObjects', [_create_object_responses(ns, assoc_objs)]) + StreamGrpc('getIdentifiables', [_create_object_responses(ns, assoc_objs)]) ], ) @@ -617,13 +618,13 @@ async def client_test(): assert result.was_failure assert isinstance(result.thrown, UnsupportedOperationException) - assert str(result.thrown) == "Identified object type 'other' is not supported by the network service" + assert str(result.thrown) == "Identifiable type 'other' is not supported by the network service" def responses(_): nio = Any() # noinspection PyUnresolvedReferences nio.Pack(Diagram(mrid=generate_id()).to_pb()) - yield GetIdentifiedObjectsResponse(identifiedObjects=[NetworkIdentifiedObject(other=nio)]) + yield GetIdentifiablesResponse(identifiables=[NetworkIdentifiable(other=nio)]) await self.mock_server.validate(client_test, [StreamGrpc('getEquipmentForContainers', [responses])]) @@ -652,69 +653,72 @@ def _assert_contains_mrids(service: BaseService, *mrids): def _response_of(io: IdentifiedObject, response_type): - return response_type(identifiedObjects=[_to_network_identified_object(io)]) + return response_type(identifiables=[_to_network_identifiable(io)]) +@deprecated("Use `_to_network_identifiable_object instead") +def _to_network_identified_object(obj: IdentifiedObject) -> NetworkIdentifiable: + return _to_network_identifiable(obj) # noinspection PyUnresolvedReferences -def _to_network_identified_object(obj) -> NetworkIdentifiedObject: +def _to_network_identifiable(obj) -> NetworkIdentifiable: if isinstance(obj, AcLineSegment): - nio = NetworkIdentifiedObject(acLineSegment=obj.to_pb()) + nio = NetworkIdentifiable(acLineSegment=obj.to_pb()) elif isinstance(obj, Breaker): - nio = NetworkIdentifiedObject(breaker=obj.to_pb()) + nio = NetworkIdentifiable(breaker=obj.to_pb()) elif isinstance(obj, EnergySource): - nio = NetworkIdentifiedObject(energySource=obj.to_pb()) + nio = NetworkIdentifiable(energySource=obj.to_pb()) elif isinstance(obj, EnergySourcePhase): - nio = NetworkIdentifiedObject(energySourcePhase=obj.to_pb()) + nio = NetworkIdentifiable(energySourcePhase=obj.to_pb()) elif isinstance(obj, Junction): - nio = NetworkIdentifiedObject(junction=obj.to_pb()) + nio = NetworkIdentifiable(junction=obj.to_pb()) elif isinstance(obj, PowerTransformer): - nio = NetworkIdentifiedObject(powerTransformer=obj.to_pb()) + nio = NetworkIdentifiable(powerTransformer=obj.to_pb()) elif isinstance(obj, Terminal): - nio = NetworkIdentifiedObject(terminal=obj.to_pb()) + nio = NetworkIdentifiable(terminal=obj.to_pb()) elif isinstance(obj, ConnectivityNode): - nio = NetworkIdentifiedObject(connectivityNode=obj.to_pb()) + nio = NetworkIdentifiable(connectivityNode=obj.to_pb()) elif isinstance(obj, CableInfo): - nio = NetworkIdentifiedObject(cableInfo=obj.to_pb()) + nio = NetworkIdentifiable(cableInfo=obj.to_pb()) elif isinstance(obj, TransformerStarImpedance): - nio = NetworkIdentifiedObject(transformerStarImpedance=obj.to_pb()) + nio = NetworkIdentifiable(transformerStarImpedance=obj.to_pb()) elif isinstance(obj, EnergySourcePhase): - nio = NetworkIdentifiedObject(energySourcePhase=obj.to_pb()) + nio = NetworkIdentifiable(energySourcePhase=obj.to_pb()) elif isinstance(obj, Feeder): - nio = NetworkIdentifiedObject(feeder=obj.to_pb()) + nio = NetworkIdentifiable(feeder=obj.to_pb()) elif isinstance(obj, Location): - nio = NetworkIdentifiedObject(location=obj.to_pb()) + nio = NetworkIdentifiable(location=obj.to_pb()) elif isinstance(obj, LvFeeder): - nio = NetworkIdentifiedObject(lvFeeder=obj.to_pb()) + nio = NetworkIdentifiable(lvFeeder=obj.to_pb()) elif isinstance(obj, LvSubstation): - nio = NetworkIdentifiedObject(lvSubstation=obj.to_pb()) + nio = NetworkIdentifiable(lvSubstation=obj.to_pb()) elif isinstance(obj, OverheadWireInfo): - nio = NetworkIdentifiedObject(overheadWireInfo=obj.to_pb()) + nio = NetworkIdentifiable(overheadWireInfo=obj.to_pb()) elif isinstance(obj, PerLengthSequenceImpedance): - nio = NetworkIdentifiedObject(perLengthSequenceImpedance=obj.to_pb()) + nio = NetworkIdentifiable(perLengthSequenceImpedance=obj.to_pb()) elif isinstance(obj, PerLengthPhaseImpedance): - nio = NetworkIdentifiedObject(perLengthPhaseImpedance=obj.to_pb()) + nio = NetworkIdentifiable(perLengthPhaseImpedance=obj.to_pb()) elif isinstance(obj, PowerTransformerEnd): - nio = NetworkIdentifiedObject(powerTransformerEnd=obj.to_pb()) + nio = NetworkIdentifiable(powerTransformerEnd=obj.to_pb()) # Currently unused # elif isinstance(obj, PanDemandResponseFunction): - nio = NetworkIdentifiedObject(panDemandResponseFunction=obj.to_pb()) + nio = NetworkIdentifiable(panDemandResponseFunction=obj.to_pb()) elif isinstance(obj, StaticVarCompensator): - nio = NetworkIdentifiedObject(staticVarCompensator=obj.to_pb()) + nio = NetworkIdentifiable(staticVarCompensator=obj.to_pb()) elif isinstance(obj, BatteryUnit): - nio = NetworkIdentifiedObject(batteryUnit=obj.to_pb()) + nio = NetworkIdentifiable(batteryUnit=obj.to_pb()) elif isinstance(obj, BatteryControl): - nio = NetworkIdentifiedObject(batteryControl=obj.to_pb()) + nio = NetworkIdentifiable(batteryControl=obj.to_pb()) # End currently unused # elif isinstance(obj, Substation): - nio = NetworkIdentifiedObject(substation=obj.to_pb()) + nio = NetworkIdentifiable(substation=obj.to_pb()) elif isinstance(obj, Loop): - nio = NetworkIdentifiedObject(loop=obj.to_pb()) + nio = NetworkIdentifiable(loop=obj.to_pb()) elif isinstance(obj, Circuit): - nio = NetworkIdentifiedObject(circuit=obj.to_pb()) + nio = NetworkIdentifiable(circuit=obj.to_pb()) elif isinstance(obj, LvFeeder): - nio = NetworkIdentifiedObject(lvFeeder=obj.to_pb()) + nio = NetworkIdentifiable(lvFeeder=obj.to_pb()) elif isinstance(obj, Pole): - nio = NetworkIdentifiedObject(pole=obj.to_pb()) + nio = NetworkIdentifiable(pole=obj.to_pb()) else: raise Exception(f"Missing class in create response - you should implement it: {str(obj)}") return nio @@ -816,14 +820,14 @@ def _validate_hierarchy(hierarchy, service): def _create_object_responses( ns: NetworkService, mrids: Optional[Iterable[str]] = None, -) -> Callable[[GetIdentifiedObjectsRequest], Generator[GetIdentifiedObjectsResponse, None, None]]: +) -> Callable[[GetIdentifiablesRequest], Generator[GetIdentifiablesResponse, None, None]]: valid: Dict[str, IdentifiedObject] = {mrid: ns[mrid] for mrid in mrids} if mrids else ns - def responses(request: GetIdentifiedObjectsRequest) -> Generator[GetIdentifiedObjectsResponse, None, None]: + def responses(request: GetIdentifiablesRequest) -> Generator[GetIdentifiablesResponse, None, None]: for mrid in request.mrids: obj = valid[mrid] if obj: - yield _response_of(obj, GetIdentifiedObjectsResponse) + yield _response_of(obj, GetIdentifiablesResponse) else: raise AssertionError(f"Requested unexpected object {mrid}.") From 5c3979d465e2fec9a044d72eccfb1deec723bb69 Mon Sep 17 00:00:00 2001 From: Max Chesterfield Date: Mon, 30 Mar 2026 12:46:11 +1100 Subject: [PATCH 2/2] fix conflicts after fixing conflicts after rebasing Signed-off-by: Max Chesterfield --- .../common/translator/base_proto2cim.py | 2 +- .../common/service_comparator_validator.py | 24 ++- test/streaming/get/pb_creators.py | 141 +++++++++--------- 3 files changed, 81 insertions(+), 86 deletions(-) diff --git a/src/zepben/ewb/services/common/translator/base_proto2cim.py b/src/zepben/ewb/services/common/translator/base_proto2cim.py index eb08281d2..80fa26388 100644 --- a/src/zepben/ewb/services/common/translator/base_proto2cim.py +++ b/src/zepben/ewb/services/common/translator/base_proto2cim.py @@ -6,7 +6,7 @@ from __future__ import annotations __all__ = ["identified_object_to_cim", "document_to_cim", "organisation_to_cim", "organisation_role_to_cim", - "BaseProtoToCim", "add_to_network_or_none", "bind_to_cim", "get_nullable"] + "BaseProtoToCim", "add_to_service_or_none", "bind_to_cim", "get_nullable"] import functools import inspect diff --git a/test/services/common/service_comparator_validator.py b/test/services/common/service_comparator_validator.py index 09ec01c5d..272ef001e 100644 --- a/test/services/common/service_comparator_validator.py +++ b/test/services/common/service_comparator_validator.py @@ -2,17 +2,15 @@ # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at https://mozilla.org/MPL/2.0/. -from types import MemberDescriptorType -from typing import Optional, Any, Callable, TypeVar, Union, Type, Set, Generic, TypeAlias from __future__ import annotations __all__ = ["ServiceComparatorValidator"] -from typing import Optional, Any, Callable, TypeVar, Union, Set, Protocol, Generic, overload, Type, Concatenate +from typing import Optional, Any, Callable, TypeVar, Union, Set, Generic, Type, Concatenate, TypeAlias from zepben.ewb import (IdentifiedObject, TIdentifiedObject, ObjectDifference, BaseService, CollectionDifference, - Difference, ReferenceDifference, ValueDifference, IndexedDifference, TIdentifiable, BaseServiceComparator, Identifiable) + Difference, ReferenceDifference, ValueDifference, IndexedDifference, TIdentifiable) from zepben.ewb.model.cim.iec61970.base.core.name_type import NameType from zepben.ewb.services.network.network_service_comparator import NetworkServiceComparatorOptions @@ -98,9 +96,9 @@ def validate_compare( def validate_property( self, - prop: Property | R, # Isn't actually R, but that is what the type checker thinks when passing class member references. + prop: property | R, # Isn't actually R, but that is what the type checker thinks when passing class member references. creator: Type[TIdentifiable] | Callable[[str], TIdentifiable], # Update to TCreator[TIdentifiable] when available. - create_value: Callable[[TIdentifiablet], R], + create_value: Callable[[TIdentifiable], R], create_other_value: Callable[[TIdentifiable], R], options: NetworkServiceComparatorOptions = NetworkServiceComparatorOptions(), options_stop_compare: bool = False, @@ -124,7 +122,7 @@ def validate_property( def validate_val_property( self, - prop: Property, + prop: property, creator: Type[TIdentifiable] | Callable[[str], TIdentifiable], # Update to TCreator[TIdentifiable] when available. change_state: Callable[[TIdentifiable, R], Any], other_change_state: Callable[[TIdentifiable, R], Any], @@ -149,7 +147,7 @@ def validate_val_property( def validate_collection( self, - prop: Property, + prop: property, add_to_collection: TAddr[TIdentifiable, R], creator: Type[TIdentifiable] | Callable[[str], TIdentifiable], # Update to TCreator[TIdentifiable] when available. create_item: Callable[[TIdentifiable], R], @@ -232,7 +230,7 @@ def validate_name_collection( def validate_indexed_collection( self, - prop: Property, + prop: property, add_to_collection: TAddr, creator: Type[TIdentifiable] | Callable[[str], TIdentifiable], # Update to TCreator[TIdentifiable] when available. create_item: Callable[[TIdentifiable], R], @@ -280,12 +278,12 @@ def get_item(obj) -> Optional[R]: def validate_unordered_collection( self, - prop: Property, + prop: property, add_to_collection: TAddr, creator: Type[TIdentifiable] | Callable[[str], TIdentifiable], # Update to TCreator[TIdentifiable] when available. - create_item_1: Callable[[K], R], - create_item_2: Callable[[K], R], - create_diff_item_1: Callable[[K], R], + create_item_1: Callable[[TIdentifiable], R], + create_item_2: Callable[[TIdentifiable], R], + create_diff_item_1: Callable[[TIdentifiable], R], options: NetworkServiceComparatorOptions = NetworkServiceComparatorOptions(), options_stop_compare: bool = False, expected_differences: Set[str] = None diff --git a/test/streaming/get/pb_creators.py b/test/streaming/get/pb_creators.py index c04c89400..a7c0d756c 100644 --- a/test/streaming/get/pb_creators.py +++ b/test/streaming/get/pb_creators.py @@ -9,9 +9,6 @@ from zepben.protobuf.dc.dc_data_pb2 import DiagramIdentifiable from zepben.protobuf.nc.nc_data_pb2 import NetworkIdentifiable from hypothesis.strategies import composite -from zepben.protobuf.cc.cc_data_pb2 import CustomerIdentifiedObject -from zepben.protobuf.dc.dc_data_pb2 import DiagramIdentifiedObject -from zepben.protobuf.nc.nc_data_pb2 import NetworkIdentifiedObject from cim.fill_fields import * @@ -96,133 +93,133 @@ def network_identified_objects(draw): # IEC61968 Common # ################### - draw(create_location().map(lambda it: NetworkIdentifiedObject(location=it.to_pb()))), - draw(create_organisation().map(lambda it: NetworkIdentifiedObject(organisation=it.to_pb()))), + draw(create_location().map(lambda it: NetworkIdentifiable(location=it.to_pb()))), + draw(create_organisation().map(lambda it: NetworkIdentifiable(organisation=it.to_pb()))), ##################################### # IEC61968 InfIEC61968 InfAssetInfo # ##################################### - draw(create_current_transformer_info().map(lambda it: NetworkIdentifiedObject(currentTransformerInfo=it.to_pb()))), - draw(create_potential_transformer_info().map(lambda it: NetworkIdentifiedObject(potentialTransformerInfo=it.to_pb()))), + draw(create_current_transformer_info().map(lambda it: NetworkIdentifiable(currentTransformerInfo=it.to_pb()))), + draw(create_potential_transformer_info().map(lambda it: NetworkIdentifiable(potentialTransformerInfo=it.to_pb()))), ################################## # IEC61968 InfIEC61968 InfAssets # ################################## - draw(create_pole().map(lambda it: NetworkIdentifiedObject(pole=it.to_pb()))), + draw(create_pole().map(lambda it: NetworkIdentifiable(pole=it.to_pb()))), ##################### # IEC61968 Metering # ##################### - draw(create_meter().map(lambda it: NetworkIdentifiedObject(meter=it.to_pb()))), - draw(create_usage_point().map(lambda it: NetworkIdentifiedObject(usagePoint=it.to_pb()))), + draw(create_meter().map(lambda it: NetworkIdentifiable(meter=it.to_pb()))), + draw(create_usage_point().map(lambda it: NetworkIdentifiable(usagePoint=it.to_pb()))), ####################### # IEC61968 Operations # ####################### - draw(create_operational_restriction().map(lambda it: NetworkIdentifiedObject(operationalRestriction=it.to_pb()))), + draw(create_operational_restriction().map(lambda it: NetworkIdentifiable(operationalRestriction=it.to_pb()))), ##################################### # IEC61970 Base Auxiliary Equipment # ##################################### - draw(create_current_transformer().map(lambda it: NetworkIdentifiedObject(currentTransformer=it.to_pb()))), - draw(create_fault_indicator().map(lambda it: NetworkIdentifiedObject(faultIndicator=it.to_pb()))), - draw(create_potential_transformer().map(lambda it: NetworkIdentifiedObject(potentialTransformer=it.to_pb()))), + draw(create_current_transformer().map(lambda it: NetworkIdentifiable(currentTransformer=it.to_pb()))), + draw(create_fault_indicator().map(lambda it: NetworkIdentifiable(faultIndicator=it.to_pb()))), + draw(create_potential_transformer().map(lambda it: NetworkIdentifiable(potentialTransformer=it.to_pb()))), ###################### # IEC61970 Base Core # ###################### - draw(create_base_voltage().map(lambda it: NetworkIdentifiedObject(baseVoltage=it.to_pb()))), - draw(create_connectivity_node().map(lambda it: NetworkIdentifiedObject(connectivityNode=it.to_pb()))), - draw(create_feeder().map(lambda it: NetworkIdentifiedObject(feeder=it.to_pb()))), - draw(create_geographical_region().map(lambda it: NetworkIdentifiedObject(geographicalRegion=it.to_pb()))), - draw(create_sub_geographical_region().map(lambda it: NetworkIdentifiedObject(subGeographicalRegion=it.to_pb()))), - draw(create_substation().map(lambda it: NetworkIdentifiedObject(substation=it.to_pb()))), - draw(create_terminal().map(lambda it: NetworkIdentifiedObject(terminal=it.to_pb()))), + draw(create_base_voltage().map(lambda it: NetworkIdentifiable(baseVoltage=it.to_pb()))), + draw(create_connectivity_node().map(lambda it: NetworkIdentifiable(connectivityNode=it.to_pb()))), + draw(create_feeder().map(lambda it: NetworkIdentifiable(feeder=it.to_pb()))), + draw(create_geographical_region().map(lambda it: NetworkIdentifiable(geographicalRegion=it.to_pb()))), + draw(create_sub_geographical_region().map(lambda it: NetworkIdentifiable(subGeographicalRegion=it.to_pb()))), + draw(create_substation().map(lambda it: NetworkIdentifiable(substation=it.to_pb()))), + draw(create_terminal().map(lambda it: NetworkIdentifiable(terminal=it.to_pb()))), ############################# # IEC61970 Base Equivalents # ############################# - draw(create_equivalent_branch().map(lambda it: NetworkIdentifiedObject(equivalentBranch=it.to_pb()))), + draw(create_equivalent_branch().map(lambda it: NetworkIdentifiable(equivalentBranch=it.to_pb()))), ####################################### # IEC61970 Base Generation Production # ####################################### - draw(create_battery_unit().map(lambda it: NetworkIdentifiedObject(batteryUnit=it.to_pb()))), - draw(create_photo_voltaic_unit().map(lambda it: NetworkIdentifiedObject(photoVoltaicUnit=it.to_pb()))), - draw(create_power_electronics_wind_unit().map(lambda it: NetworkIdentifiedObject(powerElectronicsWindUnit=it.to_pb()))), + draw(create_battery_unit().map(lambda it: NetworkIdentifiable(batteryUnit=it.to_pb()))), + draw(create_photo_voltaic_unit().map(lambda it: NetworkIdentifiable(photoVoltaicUnit=it.to_pb()))), + draw(create_power_electronics_wind_unit().map(lambda it: NetworkIdentifiable(powerElectronicsWindUnit=it.to_pb()))), ###################### # IEC61970 Base Meas # ###################### - draw(create_accumulator().map(lambda it: NetworkIdentifiedObject(accumulator=it.to_pb()))), - draw(create_analog().map(lambda it: NetworkIdentifiedObject(analog=it.to_pb()))), - draw(create_control().map(lambda it: NetworkIdentifiedObject(control=it.to_pb()))), - draw(create_discrete().map(lambda it: NetworkIdentifiedObject(discrete=it.to_pb()))), + draw(create_accumulator().map(lambda it: NetworkIdentifiable(accumulator=it.to_pb()))), + draw(create_analog().map(lambda it: NetworkIdentifiable(analog=it.to_pb()))), + draw(create_control().map(lambda it: NetworkIdentifiable(control=it.to_pb()))), + draw(create_discrete().map(lambda it: NetworkIdentifiable(discrete=it.to_pb()))), ############################ # IEC61970 Base Protection # ############################ - draw(create_current_relay().map(lambda it: NetworkIdentifiedObject(currentRelay=it.to_pb()))), + draw(create_current_relay().map(lambda it: NetworkIdentifiable(currentRelay=it.to_pb()))), ####################### # IEC61970 Base Scada # ####################### - draw(create_remote_control().map(lambda it: NetworkIdentifiedObject(remoteControl=it.to_pb()))), - draw(create_remote_source().map(lambda it: NetworkIdentifiedObject(remoteSource=it.to_pb()))), + draw(create_remote_control().map(lambda it: NetworkIdentifiable(remoteControl=it.to_pb()))), + draw(create_remote_source().map(lambda it: NetworkIdentifiable(remoteSource=it.to_pb()))), ####################### # IEC61970 Base Wires # ####################### - draw(create_ac_line_segment().map(lambda it: NetworkIdentifiedObject(acLineSegment=it.to_pb()))), - draw(create_breaker().map(lambda it: NetworkIdentifiedObject(breaker=it.to_pb()))), - draw(create_busbar_section().map(lambda it: NetworkIdentifiedObject(busbarSection=it.to_pb()))), - draw(create_clamp().map(lambda it: NetworkIdentifiedObject(clamp=it.to_pb()))), - draw(create_cut().map(lambda it: NetworkIdentifiedObject(cut=it.to_pb()))), - draw(create_disconnector().map(lambda it: NetworkIdentifiedObject(disconnector=it.to_pb()))), - draw(create_energy_consumer().map(lambda it: NetworkIdentifiedObject(energyConsumer=it.to_pb()))), - draw(create_energy_consumer_phase().map(lambda it: NetworkIdentifiedObject(energyConsumerPhase=it.to_pb()))), - draw(create_energy_source().map(lambda it: NetworkIdentifiedObject(energySource=it.to_pb()))), - draw(create_energy_source_phase().map(lambda it: NetworkIdentifiedObject(energySourcePhase=it.to_pb()))), - draw(create_fuse().map(lambda it: NetworkIdentifiedObject(fuse=it.to_pb()))), - draw(create_ground().map(lambda it: NetworkIdentifiedObject(ground=it.to_pb()))), - draw(create_ground_disconnector().map(lambda it: NetworkIdentifiedObject(groundDisconnector=it.to_pb()))), - draw(create_grounding_impedance().map(lambda it: NetworkIdentifiedObject(groundingImpedance=it.to_pb()))), - draw(create_jumper().map(lambda it: NetworkIdentifiedObject(jumper=it.to_pb()))), - draw(create_junction().map(lambda it: NetworkIdentifiedObject(junction=it.to_pb()))), - draw(create_linear_shunt_compensator().map(lambda it: NetworkIdentifiedObject(linearShuntCompensator=it.to_pb()))), - draw(create_load_break_switch().map(lambda it: NetworkIdentifiedObject(loadBreakSwitch=it.to_pb()))), - draw(create_per_length_phase_impedance().map(lambda it: NetworkIdentifiedObject(perLengthPhaseImpedance=it.to_pb()))), - draw(create_per_length_sequence_impedance().map(lambda it: NetworkIdentifiedObject(perLengthSequenceImpedance=it.to_pb()))), - draw(create_power_electronics_connection().map(lambda it: NetworkIdentifiedObject(powerElectronicsConnection=it.to_pb()))), - draw(create_power_electronics_connection_phase().map(lambda it: NetworkIdentifiedObject(powerElectronicsConnectionPhase=it.to_pb()))), - draw(create_power_transformer().map(lambda it: NetworkIdentifiedObject(powerTransformer=it.to_pb()))), - draw(create_power_transformer_end().map(lambda it: NetworkIdentifiedObject(powerTransformerEnd=it.to_pb()))), - draw(create_ratio_tap_changer().map(lambda it: NetworkIdentifiedObject(ratioTapChanger=it.to_pb()))), - draw(create_reactive_capability_curve().map(lambda it: NetworkIdentifiedObject(reactiveCapabilityCurve=it.to_pb()))), - draw(create_recloser().map(lambda it: NetworkIdentifiedObject(recloser=it.to_pb()))), - draw(create_series_compensator().map(lambda it: NetworkIdentifiedObject(seriesCompensator=it.to_pb()))), - draw(create_static_var_compensator().map(lambda it: NetworkIdentifiedObject(staticVarCompensator=it.to_pb()))), - draw(create_synchronous_machine().map(lambda it: NetworkIdentifiedObject(synchronousMachine=it.to_pb()))), - draw(create_tap_changer_control().map(lambda it: NetworkIdentifiedObject(tapChangerControl=it.to_pb()))), - draw(create_transformer_star_impedance().map(lambda it: NetworkIdentifiedObject(transformerStarImpedance=it.to_pb()))), + draw(create_ac_line_segment().map(lambda it: NetworkIdentifiable(acLineSegment=it.to_pb()))), + draw(create_breaker().map(lambda it: NetworkIdentifiable(breaker=it.to_pb()))), + draw(create_busbar_section().map(lambda it: NetworkIdentifiable(busbarSection=it.to_pb()))), + draw(create_clamp().map(lambda it: NetworkIdentifiable(clamp=it.to_pb()))), + draw(create_cut().map(lambda it: NetworkIdentifiable(cut=it.to_pb()))), + draw(create_disconnector().map(lambda it: NetworkIdentifiable(disconnector=it.to_pb()))), + draw(create_energy_consumer().map(lambda it: NetworkIdentifiable(energyConsumer=it.to_pb()))), + draw(create_energy_consumer_phase().map(lambda it: NetworkIdentifiable(energyConsumerPhase=it.to_pb()))), + draw(create_energy_source().map(lambda it: NetworkIdentifiable(energySource=it.to_pb()))), + draw(create_energy_source_phase().map(lambda it: NetworkIdentifiable(energySourcePhase=it.to_pb()))), + draw(create_fuse().map(lambda it: NetworkIdentifiable(fuse=it.to_pb()))), + draw(create_ground().map(lambda it: NetworkIdentifiable(ground=it.to_pb()))), + draw(create_ground_disconnector().map(lambda it: NetworkIdentifiable(groundDisconnector=it.to_pb()))), + draw(create_grounding_impedance().map(lambda it: NetworkIdentifiable(groundingImpedance=it.to_pb()))), + draw(create_jumper().map(lambda it: NetworkIdentifiable(jumper=it.to_pb()))), + draw(create_junction().map(lambda it: NetworkIdentifiable(junction=it.to_pb()))), + draw(create_linear_shunt_compensator().map(lambda it: NetworkIdentifiable(linearShuntCompensator=it.to_pb()))), + draw(create_load_break_switch().map(lambda it: NetworkIdentifiable(loadBreakSwitch=it.to_pb()))), + draw(create_per_length_phase_impedance().map(lambda it: NetworkIdentifiable(perLengthPhaseImpedance=it.to_pb()))), + draw(create_per_length_sequence_impedance().map(lambda it: NetworkIdentifiable(perLengthSequenceImpedance=it.to_pb()))), + draw(create_power_electronics_connection().map(lambda it: NetworkIdentifiable(powerElectronicsConnection=it.to_pb()))), + draw(create_power_electronics_connection_phase().map(lambda it: NetworkIdentifiable(powerElectronicsConnectionPhase=it.to_pb()))), + draw(create_power_transformer().map(lambda it: NetworkIdentifiable(powerTransformer=it.to_pb()))), + draw(create_power_transformer_end().map(lambda it: NetworkIdentifiable(powerTransformerEnd=it.to_pb()))), + draw(create_ratio_tap_changer().map(lambda it: NetworkIdentifiable(ratioTapChanger=it.to_pb()))), + draw(create_reactive_capability_curve().map(lambda it: NetworkIdentifiable(reactiveCapabilityCurve=it.to_pb()))), + draw(create_recloser().map(lambda it: NetworkIdentifiable(recloser=it.to_pb()))), + draw(create_series_compensator().map(lambda it: NetworkIdentifiable(seriesCompensator=it.to_pb()))), + draw(create_static_var_compensator().map(lambda it: NetworkIdentifiable(staticVarCompensator=it.to_pb()))), + draw(create_synchronous_machine().map(lambda it: NetworkIdentifiable(synchronousMachine=it.to_pb()))), + draw(create_tap_changer_control().map(lambda it: NetworkIdentifiable(tapChangerControl=it.to_pb()))), + draw(create_transformer_star_impedance().map(lambda it: NetworkIdentifiable(transformerStarImpedance=it.to_pb()))), ############################### # IEC61970 InfIEC61970 Feeder # ############################### - draw(create_circuit().map(lambda it: NetworkIdentifiedObject(circuit=it.to_pb()))), + draw(create_circuit().map(lambda it: NetworkIdentifiable(circuit=it.to_pb()))), ] return nios @@ -239,8 +236,8 @@ def diagram_identified_objects(draw): # IEC61970 Base Diagram Layout # ################################ - draw(create_diagram().map(lambda it: DiagramIdentifiedObject(diagram=it.to_pb()))), - draw(create_diagram_object().map(lambda it: DiagramIdentifiedObject(diagramObject=it.to_pb()))) + draw(create_diagram().map(lambda it: DiagramIdentifiable(diagram=it.to_pb()))), + draw(create_diagram_object().map(lambda it: DiagramIdentifiable(diagramObject=it.to_pb()))) ] return dios @@ -257,15 +254,15 @@ def customer_identified_objects(draw): # IEC61968 Common # ################### - draw(create_organisation().map(lambda it: CustomerIdentifiedObject(organisation=it.to_pb()))), + draw(create_organisation().map(lambda it: CustomerIdentifiable(organisation=it.to_pb()))), ###################### # IEC61968 Customers # ###################### - draw(create_customer().map(lambda it: CustomerIdentifiedObject(customer=it.to_pb()))), - draw(create_customer_agreement().map(lambda it: CustomerIdentifiedObject(customerAgreement=it.to_pb()))), - draw(create_pricing_structure().map(lambda it: CustomerIdentifiedObject(pricingStructure=it.to_pb()))), - draw(create_tariff().map(lambda it: CustomerIdentifiedObject(tariff=it.to_pb()))), + draw(create_customer().map(lambda it: CustomerIdentifiable(customer=it.to_pb()))), + draw(create_customer_agreement().map(lambda it: CustomerIdentifiable(customerAgreement=it.to_pb()))), + draw(create_pricing_structure().map(lambda it: CustomerIdentifiable(pricingStructure=it.to_pb()))), + draw(create_tariff().map(lambda it: CustomerIdentifiable(tariff=it.to_pb()))), ] return dios