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
3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
### Breaking Changes
* Updated `EwbDataFilePaths` to be an abstract class that supports variants. Added `LocalEwbDataFilePaths` which is a local file system implementation of
`EwbDataFilePaths`, and should be used in place of the old `EwbDataFilePaths`.
* `CopyableUUID` has been removed, and replaced with a new `generate_id` function.
* All `IdentifiedObject` classes now require an `mrid` to be passed to the constructor, it will no longer be generated by default. This brings the Python SDK
into alignment with the JVM SDK. You can use the new `generate_id` function if you can't provide a more meaningful mRID.

### New Features
* None.
Expand Down
41 changes: 20 additions & 21 deletions docs/docs/datamodel.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,13 @@ Let's see how we create them:
from zepben.ewb import EnergySource, AcLineSegment, Breaker

# Create the energy source. Providing no ID will generate a UUID.
source = EnergySource()
source = EnergySource(mrid="source")

# Create the conductor providing a specific ID.
acls = AcLineSegment("aclineseg1")
acls = AcLineSegment(mrid="aclineseg1")

# Create a circuit breaker.
# A UUID will be generated but we can give it a descriptive name.
breaker = Breaker(name="my circuit breaker")
# Create a circuit breaker with a descriptive name.
breaker = Breaker(mrid="breaker", name="my circuit breaker")
```

## Creating Connectivity
Expand All @@ -69,31 +68,31 @@ Now, lets redo the above code sample this time also creating connectivity betwee
from zepben.ewb import EnergySource, AcLineSegment, Terminal, Breaker, ConnectivityNode

# Create the energy source
source = EnergySource()
source = EnergySource(mrid="source")

# Create the terminal for the energy source and associate it with the source
source_t1 = Terminal(conducting_equipment=source)
source_t1 = Terminal(mrid="source-t1", conducting_equipment=source)
source.add_terminal(source_t1)

# Create the conductor
acls = AcLineSegment()
acls = AcLineSegment(mrid="acls")

# Create a terminal for each end of the conductor
# and associate them with the conductor
acls_t1 = Terminal(conducting_equipment=acls)
acls_t2 = Terminal(conducting_equipment=acls)
acls_t1 = Terminal(mrid="acls-t1", conducting_equipment=acls)
acls_t2 = Terminal(mrid="acls-t2", conducting_equipment=acls)
acls.add_terminal(acls_t1)
acls.add_terminal(acls_t2)

# Create a circuit breaker
breaker = Breaker()
breaker = Breaker(mrid="breaker")

# Create a terminal for the breaker
breaker_t1 = Terminal(conducting_equipment=breaker)
breaker_t1 = Terminal(mrid="breaker-t1", conducting_equipment=breaker)

# Now create a connectivity node to connect the source terminal
# to the conductor's first terminal
cn1 = ConnectivityNode()
cn1 = ConnectivityNode(mrid="cn1")

# Now associate the connectivity nodes to the terminals
cn1.add_terminal(source_t1)
Expand All @@ -103,7 +102,7 @@ acls_t1.connectivity_node = cn1

# Now create a connectivity node to connect the source terminal
# to the conductor's first terminal
cn2 = ConnectivityNode()
cn2 = ConnectivityNode(mrid="cn2")

# Now associate the connectivity nodes to the terminals
cn2.add_terminal(acls_t2)
Expand All @@ -124,7 +123,7 @@ analytics you may be running when using the model.
from zepben.ewb import Breaker

# Example of setting normal and current switch states
switch = Breaker()
switch = Breaker(mrid="switch")
switch.set_normally_open(True)
switch.set_open(False)
```
Expand Down Expand Up @@ -217,19 +216,19 @@ containers.
```python
from zepben.ewb import GeographicalRegion, SubGeographicalRegion, PowerTransformer, Feeder, Breaker

region = GeographicalRegion()
sub_region = SubGeographicalRegion(geographical_region=region)
region = GeographicalRegion(mrid="COMPANY")
sub_region = SubGeographicalRegion(mrid="BSP1", geographical_region=region)
region.add_sub_geographical_region(sub_region)

substation = Substation(sub_geographical_region=sub_region)
substation = Substation(mrid="ZONE", sub_geographical_region=sub_region)

sub_tx = PowerTransformer()
sub_tx = PowerTransformer(mrid="tx1")
sub_tx.add_container(substation)
substation.add_equipment(sub_tx)

feeder = Feeder(normal_energizing_substation=substation)
feeder = Feeder(mrid="ZONE001", normal_energizing_substation=substation)

feeder_cb = Breaker()
feeder_cb = Breaker(mrid="ZONE001-CB")
feeder_cb.add_container(feeder)
feeder.add_equipment(this)
```
Expand Down
26 changes: 13 additions & 13 deletions docs/docs/services.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ will be present, which should be utilised to allow efficient querying of the typ
from zepben.ewb import NetworkService, Breaker, Junction, IdentifiedObject
service = NetworkService()

breaker = Breaker()
breaker = Breaker(mrid="breaker")
service.add(breaker)

# Note you can only add types that are intended for the corresponding service, for example, the following will fail on add:
diagram = Diagram()
diagram = Diagram(mrid="diagram")
service.add(diagram) # throws exception.
```

Expand Down Expand Up @@ -70,8 +70,8 @@ from zepben.ewb import NetworkService, Breaker, Junction, ConductingEquipment

service = NetworkService()

service.add(Breaker())
service.add(Junction())
service.add(Breaker(mrid="breaker"))
service.add(Junction(mrid="junction"))

# service.objects() can be used to get a generator over the objects in the service, and supports selecting by type.
for obj in service.objects(): # generator over all objects in service
Expand Down Expand Up @@ -103,8 +103,8 @@ from zepben.ewb import NetworkService, Analog, Accumulator, Measurement

service = NetworkService()

amps = Analog(power_system_resource_mrid="ASWITCH")
count = Accumulator(power_system_resource_mrid="ASWITCH")
amps = Analog(mrid="amps", power_system_resource_mrid="ASWITCH")
count = Accumulator(mrid="count", power_system_resource_mrid="ASWITCH")

service.add(amps)
service.add(count)
Expand Down Expand Up @@ -141,13 +141,13 @@ from zepben.ewb import DiagramService, Diagram, DiagramObject

service = DiagramService()

a_diagram = Diagram()
a_diagram = Diagram(mrid="diagram")

do1 = DiagramObject(diagram=a_diagram, identified_object_mrid="aSwitch", a_diagram.add_diagram_object(this))
do1 = DiagramObject(mrid="do1", diagram=a_diagram, identified_object_mrid="aSwitch", a_diagram.add_diagram_object(this))

do2 = DiagramObject(diagram=a_diagram, identified_object_mrid="aSwitch", a_diagram.add_diagram_object(this))
do2 = DiagramObject(mrid="do2", diagram=a_diagram, identified_object_mrid="aSwitch", a_diagram.add_diagram_object(this))

do3 = DiagramObject(diagram=a_diagram, identified_object_mrid="aSwitch", a_diagram.add_diagram_object(this))
do3 = DiagramObject(mrid="do3", diagram=a_diagram, identified_object_mrid="aSwitch", a_diagram.add_diagram_object(this))

service.add(do1)
service.add(do2)
Expand Down Expand Up @@ -186,18 +186,18 @@ from zepben.ewb import NetworkService, Feeder, Breaker, resolver

service = NetworkService()

feeder = Feeder("f")
feeder = Feeder(mrid="f")
service.add(feeder)

switch = Breaker("b1")
switch = Breaker(mrid="b1")
service.add(switch)

# As the switch is already added to the service, this will be resolved immediately.
service.resolve_or_defer_reference(resolver.ec_equipment(feeder), switch.mrid)
print(feeder.equipment.contains(switch)) # true

# Now if we try and resolve something not added it will be deferred
junction = Junction("j1")
junction = Junction(mrid="j1")
service.resolve_or_defer_reference(resolver.ec_equipment(feeder), junction.mrid)
print(feeder.equipment.contains(junction)) # false

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from zepben.ewb.dataclassy import dataclass
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, CopyableUUID, nlen, ngen, safe_remove
from zepben.ewb.util import require, nlen, ngen, safe_remove

logger = logging.getLogger(__name__)

Expand All @@ -30,7 +30,7 @@ class IdentifiedObject(object, metaclass=ABCMeta):
relation, however must be in snake case to keep the phases PEP compliant.
"""

mrid: str = CopyableUUID()
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."""

Expand All @@ -46,6 +46,9 @@ class IdentifiedObject(object, metaclass=ABCMeta):

def __init__(self, names: Optional[List[Name]] = None, **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.")

if names:
for name in names:
self.add_name(name.type, name.name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ class RegulatingCondEq(EnergyConnection):

def __init__(self, regulating_control: Optional[RegulatingControl] = None, **kwargs):
super(RegulatingCondEq, self).__init__(**kwargs)
self.regulating_control = regulating_control
if regulating_control:
self.regulating_control = regulating_control

@property
def regulating_control(self):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def document_to_cim(pb: PBDocument, cim: Document, service: BaseService):
@bind_to_cim
@add_to_network_or_none
def organisation_to_cim(pb: PBOrganisation, service: BaseService) -> Optional[Organisation]:
cim = Organisation()
cim = Organisation(mrid=pb.mrid())

identified_object_to_cim(pb.io, cim, service)
return cim
Expand Down
10 changes: 5 additions & 5 deletions src/zepben/ewb/services/network/network_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from zepben.ewb.model.cim.iec61970.base.wires.power_transformer import PowerTransformer
from zepben.ewb.model.cim.iec61970.base.wires.power_transformer_end import PowerTransformerEnd
from zepben.ewb.services.network.network_service import NetworkService
from zepben.ewb.util import CopyableUUID
from zepben.ewb.util import generate_id


# !! WARNING !! #
Expand All @@ -39,7 +39,7 @@ def create_two_winding_power_transformer(network_service: NetworkService, cn1: C
_connect_two_terminal_conducting_equipment(network_service=network_service, ce=power_transformer, cn1=cn1, cn2=cn2)
# TODO: How to associated PowerTransformerEndInfo to a PowerTransformerInfo
for i in range(1, 2):
end = PowerTransformerEnd(power_transformer=power_transformer)
end = PowerTransformerEnd(f"{power_transformer.mrid}-pte{i}", power_transformer=power_transformer)
power_transformer.add_end(end)
end.terminal = power_transformer.get_terminal_by_sn(i)
return power_transformer
Expand Down Expand Up @@ -69,7 +69,7 @@ def create_breaker(network_service: NetworkService, cn1: ConnectivityNode, cn2:
def create_bus(network_service: NetworkService, **kwargs) -> Junction:
bus = Junction(**kwargs)
if 'mrid' not in kwargs:
bus.mrid = str(CopyableUUID())
bus.mrid = generate_id()
network_service.add(bus)
_create_terminals(ce=bus, network=network_service)
# TODO: Figure out how to add Voltage to Buses - Looks like we need to add topologicalNode to support the
Expand All @@ -79,7 +79,7 @@ def create_bus(network_service: NetworkService, **kwargs) -> Junction:

def _create_two_terminal_conducting_equipment(network_service: NetworkService, ce: ConductingEquipment, **kwargs):
if 'mrid' not in kwargs:
ce.mrid = str(CopyableUUID())
ce.mrid = generate_id()
network_service.add(ce)
_create_terminals(ce=ce, num_terms=2, network=network_service)

Expand All @@ -92,7 +92,7 @@ def _connect_two_terminal_conducting_equipment(network_service: NetworkService,

def _create_single_terminal_conducting_equipment(network_service: NetworkService, ce: ConductingEquipment, **kwargs):
if 'mrid' not in kwargs:
ce.mrid = str(CopyableUUID())
ce.mrid = generate_id()
network_service.add(ce)
_create_terminals(ce=ce, network=network_service)

Expand Down
12 changes: 3 additions & 9 deletions src/zepben/ewb/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"is_none_or_empty",
"require",
"pb_or_none",
"CopyableUUID",
"generate_id",
"datetime_to_timestamp",
"none",
"classproperty",
Expand Down Expand Up @@ -170,14 +170,8 @@ def none(collection: Collection):
raise ValueError("none() only supports collection types")


class CopyableUUID(UUID):

def __init__(self):
super().__init__(bytes=os.urandom(16), version=4)

@staticmethod
def copy():
return str(UUID(bytes=os.urandom(16), version=4))
def generate_id() -> str:
return str(UUID(bytes=os.urandom(16), version=4))


class classproperty(property):
Expand Down
21 changes: 11 additions & 10 deletions test/cim/cim_creators.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
from random import choice

from streaming.get.pb_creators import lists, floats
from util import mrid_strategy
# @formatter:off

# This must be above hypothesis.strategies to avoid conflicting import with zepben.ewb.util.none
Expand Down Expand Up @@ -181,21 +182,21 @@ def create_protection_relay_function(include_runtime: bool = True):
"protection_kind": sampled_protection_kind(),
"directable": boolean_or_none(),
"power_direction": sampled_power_direction_kind(),
"sensors": lists(builds(CurrentTransformer), max_size=2),
"protected_switches": lists(builds(Breaker), max_size=2),
"schemes": lists(builds(ProtectionRelayScheme), max_size=2),
"sensors": lists(builds(CurrentTransformer, mrid=mrid_strategy), max_size=2),
"protected_switches": lists(builds(Breaker, mrid=mrid_strategy), max_size=2),
"schemes": lists(builds(ProtectionRelayScheme, mrid=mrid_strategy), max_size=2),
"time_limits": lists(floats(min_value=FLOAT_MIN, max_value=FLOAT_MAX), min_size=4, max_size=4),
"thresholds": lists(create_relay_setting(), min_size=4, max_size=4),
"relay_info": builds(RelayInfo)
"relay_info": builds(RelayInfo, mrid=mrid_strategy)
}


def create_protection_relay_scheme(include_runtime: bool = True):
return builds(
ProtectionRelayScheme,
**create_identified_object(include_runtime),
system=builds(ProtectionRelaySystem),
functions=lists(builds(CurrentRelay))
system=builds(ProtectionRelaySystem, mrid=mrid_strategy),
functions=lists(builds(CurrentRelay, mrid=mrid_strategy))
)


Expand All @@ -204,7 +205,7 @@ def create_protection_relay_system(include_runtime: bool = True):
ProtectionRelaySystem,
**create_equipment(include_runtime),
protection_kind=sampled_protection_kind(),
schemes=lists(builds(ProtectionRelayScheme))
schemes=lists(builds(ProtectionRelayScheme, mrid=mrid_strategy))
)


Expand Down Expand Up @@ -741,7 +742,7 @@ def sampled_potential_transformer_kind():
def create_sensor(include_runtime: bool = True):
return {
**create_auxiliary_equipment(include_runtime),
"relay_functions": lists(builds(CurrentRelay), max_size=10)
"relay_functions": lists(builds(CurrentRelay, mrid=mrid_strategy), max_size=10)
}


Expand Down Expand Up @@ -1334,7 +1335,7 @@ def create_fuse(include_runtime: bool = True):
return builds(
Fuse,
**create_switch(include_runtime),
function=builds(DistanceRelay)
function=builds(DistanceRelay, mrid=mrid_strategy)
)


Expand Down Expand Up @@ -1533,7 +1534,7 @@ def create_protected_switch(include_runtime: bool):
return {
**create_switch(include_runtime),
"breaking_capacity": integers(min_value=MIN_32_BIT_INTEGER, max_value=MAX_32_BIT_INTEGER),
"relay_functions": lists(builds(CurrentRelay), min_size=1, max_size=2)
"relay_functions": lists(builds(CurrentRelay, mrid=mrid_strategy), min_size=1, max_size=2)
}


Expand Down
Loading