Skip to content

Commit

Permalink
Merge pull request #36 from rte-france/fix-get_int_addr
Browse files Browse the repository at this point in the history
rename get_int_addr and improve error handling
  • Loading branch information
vermeulenthi authored Sep 18, 2023
2 parents 0afc00a + 78c5283 commit 5021f92
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @sylmoha @vermeulenthi
* @syllamoh @vermeulenthi
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
lxml~=4.9.1
pytest~=7.2.1
setuptools~=65.5.1
lxml
pytest
setuptools
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
# Fields marked as "Optional" may be commented out.
setup(
name='scl_loader', # Required
version='1.11.1', # Required
version='1.11.2', # Required
description='Outil de manipulation de SCD', # Required
long_description=LONG_DESCRIPTION, # Optional
long_description_content_type='text/markdown', # Optional (see note above)
Expand Down
87 changes: 57 additions & 30 deletions src/scl_loader/scl_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ class ServiceType(str, Enum):
SMV = 'SMV'


class SCLLoaderError(AttributeError):
pass


def _safe_convert_value(value: str) -> any:
"""
Convert a string value in typed value une valeur string en valeur typée.
Expand Down Expand Up @@ -259,7 +263,6 @@ def get_Data_Type_Definitions(self) -> dict:

return tags


class SCDNode:
"""
Basic class to compute SCD nodes
Expand Down Expand Up @@ -329,6 +332,7 @@ def __init__(self, datatypes: DataTypeTemplates, node_elem: etree.Element = None

if len(self.name) == 0:
raise AttributeError('Name cannot be set')
self._path_from_ld = None

def add_subnode_by_elem(self, elem: etree.Element):
"""
Expand Down Expand Up @@ -414,7 +418,7 @@ def get_DO_nodes(self) -> dict:
node = self
while node.parent is not None:
if isinstance(node.parent(), LN):
do_nodes[self.get_int_addr(node)] = node
do_nodes[node.get_path_from_ld()] = node
node = node.parent()
else:
self._collect_DO_nodes(self, do_nodes)
Expand Down Expand Up @@ -466,7 +470,7 @@ def get_name_subtree(self, fc_filter: str = None):
Tree of 2-tuple elements (name, [subelem])
"""
tree = {}
node_depth = len(self.get_int_addr(self).split("."))
node_depth = len(self.get_path_from_ld().split("."))

for leaf_path, leaf_node in self.get_DA_leaf_nodes().items():
if fc_filter is None or leaf_node.get_associated_fc() == fc_filter:
Expand Down Expand Up @@ -584,7 +588,7 @@ def _collect_DA_leaf_nodes(self, node, leaves: dict) -> dict:
"""
if node is not None:
if self._is_leaf(node):
leaves[self.get_int_addr(node)] = node
leaves[node.get_path_from_ld()] = node

else:
children = node.get_children()
Expand Down Expand Up @@ -612,15 +616,14 @@ def _collect_DO_nodes(self, node, do_nodes: dict) -> dict:
"""
if node is not None:
if isinstance(node, DO):
do_nodes[self.get_int_addr(node)] = node
do_nodes[node.get_path_from_ld()] = node

else:
children = node.get_children()
for child in children:
self._collect_DO_nodes(child, do_nodes)


def get_int_addr(self, node) -> str:
def get_path_from_ld(self) -> str:
"""
Get int adr of SCDNode
Expand All @@ -632,21 +635,19 @@ def get_int_addr(self, node) -> str:
Returns
-------
`str`
Return the internal address of the node
"""
assert isinstance(node, (LD, LN, DO, DA)), "Invalid SCDNode level, expect LD, LN, DO or DA"
int_addr = node.name
if node.parent is None:
logging.debug(f'SCDNode::get_int_addr: parent node of node: {self.name} is None, cannot build int_addr')
return None
ancestor = node.parent()
while ancestor is not None:
int_addr = ancestor.name + '.' + int_addr
if ancestor.parent is not None and ancestor.tag != 'LDevice':
ancestor = ancestor.parent()
else:
ancestor = None
return int_addr
Return the path of the node from LD (format LD.LN.DO.DA)
"""
if self._path_from_ld is not None:
return self._path_from_ld
assert isinstance(self, (LD, LN, DO, DA)), "Invalid SCDNode level, expect LD, LN, DO or DA"
path = self.name
if not isinstance(self, LD):
if self.parent is None:
logging.debug(f'SCDNode::get_path_from_ld: parent node of node: {self.name} is None, cannot build path')
return None
path = self.parent().get_path_from_ld() + '.' + path
self._path_from_ld = path
return path

def get_object_reference(self) -> str:
"""
Expand Down Expand Up @@ -825,10 +826,18 @@ def _manage_SDI(self, inst_node: etree.Element, current_node: bool = None):
upd_node._set_instances(inst_node)




class DA(SCDNode):
"""
Class to manage a DA / SDA / DAI
"""
def __getattr__(self, item):
raise SCLLoaderError("'{}' {} has no attribute '{}'"
.format(type(self).__name__,
self.get_parent_with_class(IED).name + '.' + self.get_path_from_ld(),
item))

def __init__(self, datatypes: DataTypeTemplates, node_elem: etree.Element = None, fullattrs: bool = False, **kwargs: dict):
"""
Constructor
Expand Down Expand Up @@ -893,6 +902,12 @@ class DO(SCDNode):
"""
Class to manage a DO / SDO / DOI
"""
def __getattr__(self, item):
raise SCLLoaderError("'{}' {} has no attribute '{}'"
.format(type(self).__name__,
self.get_parent_with_class(IED).name + '.' + self.get_path_from_ld(),
item))

def __init__(self, datatypes: DataTypeTemplates, node_elem: etree.Element = None, fullattrs: bool = False, **kwargs: dict):
"""
Constructor
Expand Down Expand Up @@ -942,6 +957,13 @@ class LN(SCDNode):
"""
Class to manage a LN
"""

def __getattr__(self, item):
raise SCLLoaderError("'{}' {} has no attribute '{}'"
.format(type(self).__name__,
self.get_parent_with_class(IED).name + '.' + self.get_path_from_ld(),
item))

def __init__(self, datatypes: DataTypeTemplates, node_elem: etree.Element = None, fullattrs: bool = False, **kwargs: dict):
"""
Constructor
Expand Down Expand Up @@ -1135,6 +1157,12 @@ class LD(SCDNode):
"""
Class to manage a LD
"""
def __getattr__(self, item):
raise SCLLoaderError("'{}' {} has no attribute '{}'"
.format(type(self).__name__,
self.get_parent_with_class(IED).name + '.' + self.get_path_from_ld(),
item))

def __init__(self, datatypes: DataTypeTemplates, node_elem: etree.Element = None, fullattrs: bool = False, **kwargs: dict):
"""
Constructor
Expand Down Expand Up @@ -1280,6 +1308,9 @@ class IED(SCDNode):
"""
DEFAULT_AP = 'PROCESS_AP'

def __getattr__(self, item):
raise SCLLoaderError("'{}' {} has no attribute '{}'".format(type(self).__name__, self.name, item))

def __init__(self, datatypes: DataTypeTemplates, node_elem: etree.Element = None, fullattrs: bool = False, **kwargs: dict):
"""
Constructor
Expand Down Expand Up @@ -1528,16 +1559,12 @@ def get_IED_by_type(self, ied_type: str) -> list:
The loaded IED object
"""

for ied1 in self._IEDs:
if hasattr(self._IEDs, 'type') and ied1['type'] == ied_type:
return ied1

ied_elems = self._get_IED_elems_by_types([ied_type])
result = []
for ied2 in ied_elems:
ied_name = ied2.get('name')
if not hasattr(self._IEDs, ied_name):
self._IEDs[ied_name] = IED(self.datatypes, ied2, self._fullattrs)
for ied_elem in ied_elems:
ied_name = ied_elem.get('name')
if ied_name not in self._IEDs:
self._IEDs[ied_name] = IED(self.datatypes, ied_elem, self._fullattrs)

result.append(self._IEDs[ied_name])

Expand Down
15 changes: 13 additions & 2 deletions tests/test_scd_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ def test_get_DA_leaf_nodes(self):
assert len(da_list) == 54166
for da in da_list.values():
assert da.tag == 'DA' or da.tag == 'BDA'
assert ied.get_int_addr(da) is not None
assert da.get_path_from_ld() is not None

assert da_list['LD_all.LLN0.OpTmh.blkEna'].name == 'blkEna'
self._end_perfo_stats()
Expand Down Expand Up @@ -586,13 +586,24 @@ def test_get_name_subtree(self):
ln = ied.PROCESS_AP.Server.LDASLD.PTRC2
with pytest.raises(AssertionError):
ied.get_name_subtree()
assert(ied.PROCESS_AP.Server.LDASLD.get_name_subtree() == ('blkEna', []))
assert(ied.PROCESS_AP.Server.LDASLD.get_name_subtree() == ('LDASLD', [('LLN0', [('Beh', [('blkEna', []), ('d', []), ('q', []), ('stVal', []), ('subEna', []), ('subID', []), ('subQ', []), ('subVal', []), ('t', [])]), ('Health', [('blkEna', []), ('d', []), ('q', []), ('stVal', []), ('subEna', []), ('subID', []), ('subQ', []), ('subVal', []), ('t', [])]), ('InRef1', [('d', []), ('intAddr', []), ('purpose', []), ('setSrcCB', []), ('setSrcRef', []), ('setTstCB', []), ('setTstRef', []), ('tstEna', [])]), ('InRef2', [('d', []), ('intAddr', []), ('purpose', []), ('setSrcCB', []), ('setSrcRef', []), ('setTstCB', []), ('setTstRef', []), ('tstEna', [])]), ('InRef3', [('d', []), ('intAddr', []), ('purpose', []), ('setSrcCB', []), ('setSrcRef', []), ('setTstCB', []), ('setTstRef', []), ('tstEna', [])]), ('InRef4', [('d', []), ('intAddr', []), ('purpose', []), ('setSrcCB', []), ('setSrcRef', []), ('setTstCB', []), ('setTstRef', []), ('tstEna', [])]), ('InRef5', [('d', []), ('intAddr', []), ('purpose', []), ('setSrcCB', []), ('setSrcRef', []), ('setTstCB', []), ('setTstRef', []), ('tstEna', [])]), ('InRef6', [('d', []), ('intAddr', []), ('purpose', []), ('setSrcCB', []), ('setSrcRef', []), ('setTstCB', []), ('setTstRef', []), ('tstEna', [])]), ('InRef7', [('d', []), ('intAddr', []), ('purpose', []), ('setSrcCB', []), ('setSrcRef', []), ('setTstCB', []), ('setTstRef', []), ('tstEna', [])]), ('Mod', [('ctlModel', []), ('blkEna', []), ('d', []), ('q', []), ('stVal', []), ('subEna', []), ('subID', []), ('subQ', []), ('subVal', []), ('t', [])]), ('NamPlt', [('ldNs', []), ('d', []), ('paramRev', []), ('valRev', []), ('vendor', [])])]), ('RBRF2', [('Beh', [('blkEna', []), ('d', []), ('q', []), ('stVal', []), ('subEna', []), ('subID', []), ('subQ', []), ('subVal', []), ('t', [])]), ('OpEx', [('d', []), ('general', []), ('neut', []), ('phsA', []), ('phsB', []), ('phsC', []), ('q', []), ('t', []), ('originSrc', [('orCat', []), ('orIdent', [])])])]), ('RBRF1', [('Beh', [('blkEna', []), ('d', []), ('q', []), ('stVal', []), ('subEna', []), ('subID', []), ('subQ', []), ('subVal', []), ('t', [])]), ('OpEx', [('d', []), ('general', []), ('neut', []), ('phsA', []), ('phsB', []), ('phsC', []), ('q', []), ('t', []), ('originSrc', [('orCat', []), ('orIdent', [])])])]), ('PTRC2', [('Beh', [('blkEna', []), ('d', []), ('q', []), ('stVal', []), ('subEna', []), ('subID', []), ('subQ', []), ('subVal', []), ('t', [])]), ('Tr', [('d', []), ('general', []), ('neut', []), ('phsA', []), ('phsB', []), ('phsC', []), ('q', []), ('t', []), ('originSrc', [('orCat', []), ('orIdent', [])])])]), ('PTRC1', [('Beh', [('blkEna', []), ('d', []), ('q', []), ('stVal', []), ('subEna', []), ('subID', []), ('subQ', []), ('subVal', []), ('t', [])]), ('Tr', [('d', []), ('general', []), ('neut', []), ('phsA', []), ('phsB', []), ('phsC', []), ('q', []), ('t', []), ('originSrc', [('orCat', []), ('orIdent', [])])])]), ('RBRF3', [('Beh', [('blkEna', []), ('d', []), ('q', []), ('stVal', []), ('subEna', []), ('subID', []), ('subQ', []), ('subVal', []), ('t', [])]), ('OpEx', [('d', []), ('general', []), ('neut', []), ('phsA', []), ('phsB', []), ('phsC', []), ('q', []), ('t', []), ('originSrc', [('orCat', []), ('orIdent', [])])])]), ('LPHD0', [('NamPlt', [('configRev', []), ('d', []), ('paramRev', []), ('swRev', []), ('valRev', []), ('vendor', [])]), ('PhyHealth', [('blkEna', []), ('d', []), ('q', []), ('stVal', []), ('subEna', []), ('subID', []), ('subQ', []), ('subVal', []), ('t', [])]), ('PhyNam', [('d', []), ('hwRev', []), ('location', []), ('mRID', []), ('model', []), ('serNum', []), ('swRev', []), ('vendor', [])]), ('Proxy', [('blkEna', []), ('d', []), ('q', []), ('stVal', []), ('subEna', []), ('subID', []), ('subQ', []), ('subVal', []), ('t', [])])]), ('PTRC3', [('Beh', [('blkEna', []), ('d', []), ('q', []), ('stVal', []), ('subEna', []), ('subID', []), ('subQ', []), ('subVal', []), ('t', [])]), ('Tr', [('d', []), ('general', []), ('neut', []), ('phsA', []), ('phsB', []), ('phsC', []), ('q', []), ('t', []), ('originSrc', [('orCat', []), ('orIdent', [])])])])]))
assert(ln.get_name_subtree() == ('PTRC2', [('Beh', [('blkEna', []), ('d', []), ('q', []), ('stVal', []), ('subEna', []), ('subID', []), ('subQ', []), ('subVal', []), ('t', [])]), ('Tr', [('d', []), ('general', []), ('neut', []), ('phsA', []), ('phsB', []), ('phsC', []), ('q', []), ('t', []), ('originSrc', [('orCat', []), ('orIdent', [])])])]))
assert(ln.Tr.d.get_name_subtree() == ('d', []) )
assert(ln.Tr.get_name_subtree() == ('Tr', [('d', []), ('general', []), ('neut', []), ('phsA', []), ('phsB', []), ('phsC', []), ('q', []), ('t', []), ('originSrc', [('orCat', []), ('orIdent', [])])]))
assert(ln.Tr.originSrc.get_name_subtree() == ('originSrc', [('orCat', []), ('orIdent', [])]))
assert(ln.get_name_subtree("ST") == ('PTRC2', [('Beh', [('q', []), ('stVal', []), ('t', [])]), ('Tr', [('general', []), ('neut', []), ('phsA', []), ('phsB', []), ('phsC', []), ('q', []), ('t', []), ('originSrc', [('orCat', []), ('orIdent', [])])])]))

def test_get_path_from_ld(self):
ied = self.SCD_HANDLER.get_IED_by_name('AUT1A_SITE_1')
ln = ied.PROCESS_AP.Server.LDASLD.PTRC2
with pytest.raises(AssertionError):
ied.get_path_from_ld()
assert ied.PROCESS_AP.Server.LDASLD.get_path_from_ld() == "LDASLD"
assert ln.get_path_from_ld() == 'LDASLD.PTRC2'
assert ln.Tr.d.get_path_from_ld() =='LDASLD.PTRC2.Tr.d'
assert ln.Tr.get_path_from_ld() == 'LDASLD.PTRC2.Tr'
assert ln.Tr.originSrc.orCat.get_path_from_ld() == 'LDASLD.PTRC2.Tr.originSrc.orCat'

def test_get_object_reference(self):
ied = self.SCD_HANDLER.get_IED_by_name('AUT1A_SITE_1')
ln = ied.PROCESS_AP.Server.LDASLD.PTRC2
Expand Down

0 comments on commit 5021f92

Please sign in to comment.