Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ to prevent the test from timing out while you step through the code:
* ```import <new_class_name> as PB<new_class_name>```
* Add ```def <new_class_name>_to_pb```
* Add ```"<new_class_name>_to_pb"``` to ```__all__```
* Add ```<new_class_name>.to_pb = <new_class_name>_to_pb```
* Annotate ```<new_class_name>_to_pb``` with the ```@bind_to_pb``` decorator
1. Update [network_proto2cim.py](src/zepben/ewb/services/network/translator/network_proto2cim.py)
* ```import <new_class_name> as PB<new_class_name>```
* Add ```def <new_class_name>_to_pb```
* Add ```"<new_class_name>_to_cim"``` to ```__all__```
* Add ```<new_class_name>_to_cim = <new_class_name>_to_cim```
* Annotate ```<new_class_name>_to_cim``` with the ```@bind_to_cim``` decorator
1. Add reference resolver(s) to resolvers in [common package](src/zepben/ewb/services/common) (if new associations).
1. Update database schema:
1. Increment `TablesVersion.SUPPORTED_VERSION` by 1 in [table_version.py](src/zepben/ewb/database/sqlite/tables/table_version.py)
Expand Down
31 changes: 21 additions & 10 deletions src/zepben/ewb/services/common/translator/base_cim2proto.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

__all__ = ["identified_object_to_pb", "document_to_pb", "organisation_role_to_pb", "organisation_to_pb"]

import inspect
from typing import ParamSpec, TypeVar, Callable

# noinspection PyPackageRequirements,PyUnresolvedReferences
from google.protobuf.timestamp_pb2 import Timestamp as PBTimestamp
from zepben.protobuf.cim.iec61968.common.Document_pb2 import Document as PBDocument
Expand All @@ -23,10 +26,23 @@
from zepben.ewb.services.common.translator.util import mrid_or_empty


P = ParamSpec("P")
R = TypeVar("R")


def bind_to_pb(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_pb` function to be the function we are wrapping
"""
inspect.get_annotations(func, eval_str=True)[func.__code__.co_varnames[0]].to_pb = func
return func

###################
# IEC61968 Common #
###################

@bind_to_pb
def document_to_pb(cim: Document) -> PBDocument:
timestamp = None
if cim.created_date_time:
Expand All @@ -44,26 +60,24 @@ def document_to_pb(cim: Document) -> PBDocument:
)


@bind_to_pb
def organisation_to_pb(cim: Organisation) -> PBOrganisation:
return PBOrganisation(io=identified_object_to_pb(cim))


@bind_to_pb
def organisation_role_to_pb(cim: OrganisationRole) -> PBOrganisationRole:
return PBOrganisationRole(
io=identified_object_to_pb(cim),
organisationMRID=mrid_or_empty(cim.organisation)
)


Document.to_pb = document_to_pb
Organisation.to_pb = organisation_to_pb
OrganisationRole.to_pb = organisation_role_to_pb


######################
# IEC61970 Base Core #
######################

@bind_to_pb
def identified_object_to_pb(cim: IdentifiedObject) -> PBIdentifiedObject:
return PBIdentifiedObject(
mRID=str(cim.mrid),
Expand All @@ -73,20 +87,17 @@ def identified_object_to_pb(cim: IdentifiedObject) -> PBIdentifiedObject:
)


@bind_to_pb
def name_to_pb(cim: Name) -> PBName:
return PBName(
name=cim.name,
type=cim.type.name if cim.type else None
)


@bind_to_pb
def name_type_to_pb(cim: NameType) -> PBNameType:
return PBNameType(
name=cim.name,
description=cim.description
)


IdentifiedObject.to_pb = identified_object_to_pb
Name.to_pb = name_to_pb
NameType.to_pb = name_type_to_pb
55 changes: 42 additions & 13 deletions src/zepben/ewb/services/common/translator/base_proto2cim.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@
# 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__ = ["identified_object_to_cim", "document_to_cim", "organisation_to_cim", "organisation_role_to_cim", "BaseProtoToCim"]
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"]

import functools
import inspect
from abc import ABCMeta
from typing import Optional
from typing import Optional, Callable, TypeVar

from google.protobuf.message import Message
from typing_extensions import ParamSpec
# noinspection PyPackageRequirements
from zepben.protobuf.cim.iec61968.common.Document_pb2 import Document as PBDocument
from zepben.protobuf.cim.iec61968.common.OrganisationRole_pb2 import OrganisationRole as PBOrganisationRole
Expand All @@ -23,10 +30,36 @@
from zepben.ewb.services.common.base_service import BaseService


TProtoToCimFunc = Callable[[Message, BaseService], Optional[IdentifiedObject]]
P = ParamSpec("P")
R = TypeVar("R")


def add_to_network_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)`
"""
@functools.wraps(func)
def wrapper(pb: Message, service: BaseService) -> Optional[IdentifiedObject]:
return cim if service.add(cim := func(pb, service)) else None
return wrapper


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
"""
inspect.get_annotations(func, eval_str=True)[func.__code__.co_varnames[0]].to_cim = func
return func


###################
# IEC61968 Common #
###################

@bind_to_cim
def document_to_cim(pb: PBDocument, cim: Document, service: BaseService):
cim.title = pb.title
cim.created_date_time = pb.createdDateTime.ToDatetime() if pb.HasField("createdDateTime") else None
Expand All @@ -38,35 +71,35 @@ def document_to_cim(pb: PBDocument, cim: Document, service: BaseService):
identified_object_to_cim(pb.io, cim, service)


@bind_to_cim
@add_to_network_or_none
def organisation_to_cim(pb: PBOrganisation, service: BaseService) -> Optional[Organisation]:
cim = Organisation()

identified_object_to_cim(pb.io, cim, service)
return cim if service.add(cim) else None
return cim


@bind_to_cim
def organisation_role_to_cim(pb: PBOrganisationRole, cim: OrganisationRole, service: BaseService):
service.resolve_or_defer_reference(resolver.organisation(cim), pb.organisationMRID)

identified_object_to_cim(pb.io, cim, service)


PBDocument.to_cim = document_to_cim
PBOrganisation.to_cim = organisation_to_cim
PBOrganisationRole.to_cim = organisation_role_to_cim


######################
# IEC61970 Base Core #
######################

@bind_to_cim
def identified_object_to_cim(pb: PBIdentifiedObject, cim: IdentifiedObject, service: BaseService):
cim.mrid = pb.mRID
cim.name = pb.name
cim.description = pb.description
[cim.add_name(name_to_cim(name, cim, service).type, name.name) for name in pb.names]


@bind_to_cim
def name_to_cim(pb: PBName, io: IdentifiedObject, service: BaseService):
try:
nt = service.get_name_type(pb.type)
Expand All @@ -78,6 +111,7 @@ def name_to_cim(pb: PBName, io: IdentifiedObject, service: BaseService):
return nt.get_or_add_name(pb.name, io)


@bind_to_cim
def name_type_to_cim(pb: PBNameType, service: BaseService):
try:
nt = service.get_name_type(pb.name)
Expand All @@ -90,11 +124,6 @@ def name_type_to_cim(pb: PBNameType, service: BaseService):
return nt


PBIdentifiedObject.to_cim = identified_object_to_cim
PBName.to_cim = name_to_cim
PBNameType.to_cim = name_type_to_cim


@dataclass(slots=True)
class BaseProtoToCim(object, metaclass=ABCMeta):
service: BaseService
Expand Down
9 changes: 3 additions & 6 deletions src/zepben/ewb/services/common/translator/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def long_or_none(value: int) -> Optional[int]:


def str_or_none(value: str) -> Optional[str]:
return value if value else None
return value or None


def from_nullable_int(value: Optional[int]) -> int:
Expand All @@ -69,10 +69,7 @@ def from_nullable_long(value: Optional[int]) -> int:


def nullable_bool_settings(flag_name: str, value: Optional[bool]) -> Dict:
settings = {}
if value is None:
settings[f"{flag_name}Null"] = NullValue.NULL_VALUE
return {f'{flag_name}Null': NullValue.NULL_VALUE}
else:
settings[f"{flag_name}Set"] = value

return settings
return {f'{flag_name}Set': value}
Loading