Skip to content

Commit c3a4604

Browse files
authored
[DEV-5510] Require mrid for all IdentifiedObject constructors. (#212)
Signed-off-by: Anthony Charlton <anthony.charlton@zepben.com>
1 parent 10bacf4 commit c3a4604

149 files changed

Lines changed: 1070 additions & 769 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
### Breaking Changes
44
* Updated `EwbDataFilePaths` to be an abstract class that supports variants. Added `LocalEwbDataFilePaths` which is a local file system implementation of
55
`EwbDataFilePaths`, and should be used in place of the old `EwbDataFilePaths`.
6+
* `CopyableUUID` has been removed, and replaced with a new `generate_id` function.
7+
* 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
8+
into alignment with the JVM SDK. You can use the new `generate_id` function if you can't provide a more meaningful mRID.
69

710
### New Features
811
* None.

docs/docs/datamodel.mdx

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,13 @@ Let's see how we create them:
4343
from zepben.ewb import EnergySource, AcLineSegment, Breaker
4444

4545
# Create the energy source. Providing no ID will generate a UUID.
46-
source = EnergySource()
46+
source = EnergySource(mrid="source")
4747

4848
# Create the conductor providing a specific ID.
49-
acls = AcLineSegment("aclineseg1")
49+
acls = AcLineSegment(mrid="aclineseg1")
5050

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

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

7170
# Create the energy source
72-
source = EnergySource()
71+
source = EnergySource(mrid="source")
7372

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

7877
# Create the conductor
79-
acls = AcLineSegment()
78+
acls = AcLineSegment(mrid="acls")
8079

8180
# Create a terminal for each end of the conductor
8281
# and associate them with the conductor
83-
acls_t1 = Terminal(conducting_equipment=acls)
84-
acls_t2 = Terminal(conducting_equipment=acls)
82+
acls_t1 = Terminal(mrid="acls-t1", conducting_equipment=acls)
83+
acls_t2 = Terminal(mrid="acls-t2", conducting_equipment=acls)
8584
acls.add_terminal(acls_t1)
8685
acls.add_terminal(acls_t2)
8786

8887
# Create a circuit breaker
89-
breaker = Breaker()
88+
breaker = Breaker(mrid="breaker")
9089

9190
# Create a terminal for the breaker
92-
breaker_t1 = Terminal(conducting_equipment=breaker)
91+
breaker_t1 = Terminal(mrid="breaker-t1", conducting_equipment=breaker)
9392

9493
# Now create a connectivity node to connect the source terminal
9594
# to the conductor's first terminal
96-
cn1 = ConnectivityNode()
95+
cn1 = ConnectivityNode(mrid="cn1")
9796

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

104103
# Now create a connectivity node to connect the source terminal
105104
# to the conductor's first terminal
106-
cn2 = ConnectivityNode()
105+
cn2 = ConnectivityNode(mrid="cn2")
107106

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

126125
# Example of setting normal and current switch states
127-
switch = Breaker()
126+
switch = Breaker(mrid="switch")
128127
switch.set_normally_open(True)
129128
switch.set_open(False)
130129
```
@@ -217,19 +216,19 @@ containers.
217216
```python
218217
from zepben.ewb import GeographicalRegion, SubGeographicalRegion, PowerTransformer, Feeder, Breaker
219218

220-
region = GeographicalRegion()
221-
sub_region = SubGeographicalRegion(geographical_region=region)
219+
region = GeographicalRegion(mrid="COMPANY")
220+
sub_region = SubGeographicalRegion(mrid="BSP1", geographical_region=region)
222221
region.add_sub_geographical_region(sub_region)
223222

224-
substation = Substation(sub_geographical_region=sub_region)
223+
substation = Substation(mrid="ZONE", sub_geographical_region=sub_region)
225224

226-
sub_tx = PowerTransformer()
225+
sub_tx = PowerTransformer(mrid="tx1")
227226
sub_tx.add_container(substation)
228227
substation.add_equipment(sub_tx)
229228

230-
feeder = Feeder(normal_energizing_substation=substation)
229+
feeder = Feeder(mrid="ZONE001", normal_energizing_substation=substation)
231230

232-
feeder_cb = Breaker()
231+
feeder_cb = Breaker(mrid="ZONE001-CB")
233232
feeder_cb.add_container(feeder)
234233
feeder.add_equipment(this)
235234
```

docs/docs/services.mdx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ will be present, which should be utilised to allow efficient querying of the typ
3636
from zepben.ewb import NetworkService, Breaker, Junction, IdentifiedObject
3737
service = NetworkService()
3838

39-
breaker = Breaker()
39+
breaker = Breaker(mrid="breaker")
4040
service.add(breaker)
4141

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

@@ -70,8 +70,8 @@ from zepben.ewb import NetworkService, Breaker, Junction, ConductingEquipment
7070

7171
service = NetworkService()
7272

73-
service.add(Breaker())
74-
service.add(Junction())
73+
service.add(Breaker(mrid="breaker"))
74+
service.add(Junction(mrid="junction"))
7575

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

104104
service = NetworkService()
105105

106-
amps = Analog(power_system_resource_mrid="ASWITCH")
107-
count = Accumulator(power_system_resource_mrid="ASWITCH")
106+
amps = Analog(mrid="amps", power_system_resource_mrid="ASWITCH")
107+
count = Accumulator(mrid="count", power_system_resource_mrid="ASWITCH")
108108

109109
service.add(amps)
110110
service.add(count)
@@ -141,13 +141,13 @@ from zepben.ewb import DiagramService, Diagram, DiagramObject
141141

142142
service = DiagramService()
143143

144-
a_diagram = Diagram()
144+
a_diagram = Diagram(mrid="diagram")
145145

146-
do1 = DiagramObject(diagram=a_diagram, identified_object_mrid="aSwitch", a_diagram.add_diagram_object(this))
146+
do1 = DiagramObject(mrid="do1", diagram=a_diagram, identified_object_mrid="aSwitch", a_diagram.add_diagram_object(this))
147147

148-
do2 = DiagramObject(diagram=a_diagram, identified_object_mrid="aSwitch", a_diagram.add_diagram_object(this))
148+
do2 = DiagramObject(mrid="do2", diagram=a_diagram, identified_object_mrid="aSwitch", a_diagram.add_diagram_object(this))
149149

150-
do3 = DiagramObject(diagram=a_diagram, identified_object_mrid="aSwitch", a_diagram.add_diagram_object(this))
150+
do3 = DiagramObject(mrid="do3", diagram=a_diagram, identified_object_mrid="aSwitch", a_diagram.add_diagram_object(this))
151151

152152
service.add(do1)
153153
service.add(do2)
@@ -186,18 +186,18 @@ from zepben.ewb import NetworkService, Feeder, Breaker, resolver
186186

187187
service = NetworkService()
188188

189-
feeder = Feeder("f")
189+
feeder = Feeder(mrid="f")
190190
service.add(feeder)
191191

192-
switch = Breaker("b1")
192+
switch = Breaker(mrid="b1")
193193
service.add(switch)
194194

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

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

src/zepben/ewb/model/cim/iec61970/base/core/identified_object.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from zepben.ewb.dataclassy import dataclass
1515
from zepben.ewb.model.cim.iec61970.base.core.name import Name
1616
from zepben.ewb.model.cim.iec61970.base.core.name_type import NameType
17-
from zepben.ewb.util import require, CopyableUUID, nlen, ngen, safe_remove
17+
from zepben.ewb.util import require, nlen, ngen, safe_remove
1818

1919
logger = logging.getLogger(__name__)
2020

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

33-
mrid: str = CopyableUUID()
33+
mrid: str
3434
"""Master resource identifier issued by a model authority. The mRID is unique within an exchange context.
3535
Global uniqueness is easily achieved by using a UUID, as specified in RFC 4122, for the mRID. The use of UUID is strongly recommended."""
3636

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

4747
def __init__(self, names: Optional[List[Name]] = None, **kwargs):
4848
super(IdentifiedObject, self).__init__(**kwargs)
49+
if not self.mrid or not self.mrid.strip():
50+
raise ValueError("You must provide an mRID for this object.")
51+
4952
if names:
5053
for name in names:
5154
self.add_name(name.type, name.name)

src/zepben/ewb/model/cim/iec61970/base/wires/regulating_cond_eq.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ class RegulatingCondEq(EnergyConnection):
2828

2929
def __init__(self, regulating_control: Optional[RegulatingControl] = None, **kwargs):
3030
super(RegulatingCondEq, self).__init__(**kwargs)
31-
self.regulating_control = regulating_control
31+
if regulating_control:
32+
self.regulating_control = regulating_control
3233

3334
@property
3435
def regulating_control(self):

src/zepben/ewb/services/common/translator/base_proto2cim.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def document_to_cim(pb: PBDocument, cim: Document, service: BaseService):
7979
@bind_to_cim
8080
@add_to_network_or_none
8181
def organisation_to_cim(pb: PBOrganisation, service: BaseService) -> Optional[Organisation]:
82-
cim = Organisation()
82+
cim = Organisation(mrid=pb.mrid())
8383

8484
identified_object_to_cim(pb.io, cim, service)
8585
return cim

src/zepben/ewb/services/network/network_extensions.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from zepben.ewb.model.cim.iec61970.base.wires.power_transformer import PowerTransformer
1919
from zepben.ewb.model.cim.iec61970.base.wires.power_transformer_end import PowerTransformerEnd
2020
from zepben.ewb.services.network.network_service import NetworkService
21-
from zepben.ewb.util import CopyableUUID
21+
from zepben.ewb.util import generate_id
2222

2323

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

8080
def _create_two_terminal_conducting_equipment(network_service: NetworkService, ce: ConductingEquipment, **kwargs):
8181
if 'mrid' not in kwargs:
82-
ce.mrid = str(CopyableUUID())
82+
ce.mrid = generate_id()
8383
network_service.add(ce)
8484
_create_terminals(ce=ce, num_terms=2, network=network_service)
8585

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

9393
def _create_single_terminal_conducting_equipment(network_service: NetworkService, ce: ConductingEquipment, **kwargs):
9494
if 'mrid' not in kwargs:
95-
ce.mrid = str(CopyableUUID())
95+
ce.mrid = generate_id()
9696
network_service.add(ce)
9797
_create_terminals(ce=ce, network=network_service)
9898

src/zepben/ewb/util.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"is_none_or_empty",
1616
"require",
1717
"pb_or_none",
18-
"CopyableUUID",
18+
"generate_id",
1919
"datetime_to_timestamp",
2020
"none",
2121
"classproperty",
@@ -170,14 +170,8 @@ def none(collection: Collection):
170170
raise ValueError("none() only supports collection types")
171171

172172

173-
class CopyableUUID(UUID):
174-
175-
def __init__(self):
176-
super().__init__(bytes=os.urandom(16), version=4)
177-
178-
@staticmethod
179-
def copy():
180-
return str(UUID(bytes=os.urandom(16), version=4))
173+
def generate_id() -> str:
174+
return str(UUID(bytes=os.urandom(16), version=4))
181175

182176

183177
class classproperty(property):

test/cim/cim_creators.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
from random import choice
4747

4848
from streaming.get.pb_creators import lists, floats
49+
from util import mrid_strategy
4950
# @formatter:off
5051

5152
# This must be above hypothesis.strategies to avoid conflicting import with zepben.ewb.util.none
@@ -181,21 +182,21 @@ def create_protection_relay_function(include_runtime: bool = True):
181182
"protection_kind": sampled_protection_kind(),
182183
"directable": boolean_or_none(),
183184
"power_direction": sampled_power_direction_kind(),
184-
"sensors": lists(builds(CurrentTransformer), max_size=2),
185-
"protected_switches": lists(builds(Breaker), max_size=2),
186-
"schemes": lists(builds(ProtectionRelayScheme), max_size=2),
185+
"sensors": lists(builds(CurrentTransformer, mrid=mrid_strategy), max_size=2),
186+
"protected_switches": lists(builds(Breaker, mrid=mrid_strategy), max_size=2),
187+
"schemes": lists(builds(ProtectionRelayScheme, mrid=mrid_strategy), max_size=2),
187188
"time_limits": lists(floats(min_value=FLOAT_MIN, max_value=FLOAT_MAX), min_size=4, max_size=4),
188189
"thresholds": lists(create_relay_setting(), min_size=4, max_size=4),
189-
"relay_info": builds(RelayInfo)
190+
"relay_info": builds(RelayInfo, mrid=mrid_strategy)
190191
}
191192

192193

193194
def create_protection_relay_scheme(include_runtime: bool = True):
194195
return builds(
195196
ProtectionRelayScheme,
196197
**create_identified_object(include_runtime),
197-
system=builds(ProtectionRelaySystem),
198-
functions=lists(builds(CurrentRelay))
198+
system=builds(ProtectionRelaySystem, mrid=mrid_strategy),
199+
functions=lists(builds(CurrentRelay, mrid=mrid_strategy))
199200
)
200201

201202

@@ -204,7 +205,7 @@ def create_protection_relay_system(include_runtime: bool = True):
204205
ProtectionRelaySystem,
205206
**create_equipment(include_runtime),
206207
protection_kind=sampled_protection_kind(),
207-
schemes=lists(builds(ProtectionRelayScheme))
208+
schemes=lists(builds(ProtectionRelayScheme, mrid=mrid_strategy))
208209
)
209210

210211

@@ -741,7 +742,7 @@ def sampled_potential_transformer_kind():
741742
def create_sensor(include_runtime: bool = True):
742743
return {
743744
**create_auxiliary_equipment(include_runtime),
744-
"relay_functions": lists(builds(CurrentRelay), max_size=10)
745+
"relay_functions": lists(builds(CurrentRelay, mrid=mrid_strategy), max_size=10)
745746
}
746747

747748

@@ -1334,7 +1335,7 @@ def create_fuse(include_runtime: bool = True):
13341335
return builds(
13351336
Fuse,
13361337
**create_switch(include_runtime),
1337-
function=builds(DistanceRelay)
1338+
function=builds(DistanceRelay, mrid=mrid_strategy)
13381339
)
13391340

13401341

@@ -1533,7 +1534,7 @@ def create_protected_switch(include_runtime: bool):
15331534
return {
15341535
**create_switch(include_runtime),
15351536
"breaking_capacity": integers(min_value=MIN_32_BIT_INTEGER, max_value=MAX_32_BIT_INTEGER),
1536-
"relay_functions": lists(builds(CurrentRelay), min_size=1, max_size=2)
1537+
"relay_functions": lists(builds(CurrentRelay, mrid=mrid_strategy), min_size=1, max_size=2)
15371538
}
15381539

15391540

0 commit comments

Comments
 (0)