-
Notifications
You must be signed in to change notification settings - Fork 44
sdk: Fix walk_submodel() skipping Entity and Operation children #465
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| # Copyright (c) 2026 the Eclipse BaSyx Authors | ||
| # | ||
| # This program and the accompanying materials are made available under the terms of the MIT License, available in | ||
| # the LICENSE file of this project. | ||
| # | ||
| # SPDX-License-Identifier: MIT | ||
|
|
||
| import unittest | ||
| from typing import List | ||
|
|
||
| from basyx.aas import model | ||
| from basyx.aas.util.traversal import walk_submodel | ||
|
|
||
|
|
||
| class TestWalkSubmodel(unittest.TestCase): | ||
| def _submodel(self, *elements: model.SubmodelElement) -> model.Submodel: | ||
| return model.Submodel("test-submodel", submodel_element=list(elements)) | ||
|
|
||
| def test_flat_submodel(self): | ||
| prop = model.Property("prop", model.datatypes.String) | ||
| sm = self._submodel(prop) | ||
| result = list(walk_submodel(sm)) | ||
| self.assertEqual([prop], result) | ||
|
|
||
| def test_collection_post_order(self): | ||
| child1 = model.Property("child1", model.datatypes.String) | ||
| child2 = model.Property("child2", model.datatypes.String) | ||
| coll = model.SubmodelElementCollection("coll", value=[child1, child2]) | ||
| sm = self._submodel(coll) | ||
| result = list(walk_submodel(sm)) | ||
| # post-order: children before parent | ||
| self.assertEqual([child1, child2, coll], result) | ||
|
Comment on lines
+25
to
+32
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was implicitly the ordering before. I'm not sure we should guarantee that ordering, though. |
||
|
|
||
| def test_list_post_order(self): | ||
| child = model.Property(None, model.datatypes.String) | ||
| sml = model.SubmodelElementList("sml", type_value_list_element=model.Property, | ||
| value_type_list_element=model.datatypes.String, value=[child]) | ||
| sm = self._submodel(sml) | ||
| result = list(walk_submodel(sm)) | ||
| self.assertEqual([child, sml], result) | ||
|
|
||
| def test_entity_statement_post_order(self): | ||
| stmt = model.Property("stmt", model.datatypes.String) | ||
| entity = model.Entity("entity", model.EntityType.CO_MANAGED_ENTITY, statement=[stmt]) | ||
| sm = self._submodel(entity) | ||
| result = list(walk_submodel(sm)) | ||
| # post-order: statement element before Entity | ||
| self.assertEqual([stmt, entity], result) | ||
|
|
||
| def test_entity_empty_statement(self): | ||
| entity = model.Entity("entity", model.EntityType.CO_MANAGED_ENTITY) | ||
| sm = self._submodel(entity) | ||
| result = list(walk_submodel(sm)) | ||
| self.assertEqual([entity], result) | ||
|
|
||
| def test_operation_variables_post_order(self): | ||
| in_var = model.Property("in_var", model.datatypes.String) | ||
| out_var = model.Property("out_var", model.datatypes.String) | ||
| inout_var = model.Property("inout_var", model.datatypes.String) | ||
| op = model.Operation("op", input_variable=[in_var], output_variable=[out_var], | ||
| in_output_variable=[inout_var]) | ||
| sm = self._submodel(op) | ||
| result = list(walk_submodel(sm)) | ||
| # post-order: all variable elements before Operation | ||
| self.assertEqual([in_var, out_var, inout_var, op], result) | ||
|
|
||
| def test_operation_empty_variables(self): | ||
| op = model.Operation("op") | ||
| sm = self._submodel(op) | ||
| result = list(walk_submodel(sm)) | ||
| self.assertEqual([op], result) | ||
|
|
||
| def test_collection_inside_entity_statement(self): | ||
| inner = model.Property("inner", model.datatypes.String) | ||
| coll = model.SubmodelElementCollection("coll", value=[inner]) | ||
| entity = model.Entity("entity", model.EntityType.CO_MANAGED_ENTITY, statement=[coll]) | ||
| sm = self._submodel(entity) | ||
| result = list(walk_submodel(sm)) | ||
| # post-order: inner → coll → entity | ||
| self.assertEqual([inner, coll, entity], result) | ||
|
|
||
| def test_entity_inside_operation_input_variable(self): | ||
| stmt = model.Property("stmt", model.datatypes.String) | ||
| entity = model.Entity("entity", model.EntityType.CO_MANAGED_ENTITY, statement=[stmt]) | ||
| op = model.Operation("op", input_variable=[entity]) | ||
| sm = self._submodel(op) | ||
| result = list(walk_submodel(sm)) | ||
| # post-order: stmt → entity → op | ||
| self.assertEqual([stmt, entity, op], result) | ||
|
|
||
| def test_walk_from_collection(self): | ||
| prop = model.Property("prop", model.datatypes.String) | ||
| entity = model.Entity("entity", model.EntityType.CO_MANAGED_ENTITY, statement=[prop]) | ||
| coll = model.SubmodelElementCollection("coll", value=[entity]) | ||
| # walk_submodel(coll) yields the contents of coll, not coll itself | ||
| result = list(walk_submodel(coll)) | ||
| self.assertEqual([prop, entity], result) | ||
|
|
||
| def test_walk_from_list(self): | ||
| op = model.Operation(None) | ||
| sml = model.SubmodelElementList("sml", type_value_list_element=model.Operation, value=[op]) | ||
| # walk_submodel(sml) yields the contents of sml, not sml itself | ||
| result = list(walk_submodel(sml)) | ||
| self.assertEqual([op], result) | ||
|
|
||
| def test_file_inside_entity_is_found(self): | ||
| """Regression test for issue #423: File inside Entity.statement must be yielded.""" | ||
| f = model.File("file", content_type="application/pdf", value="/some/file.pdf") | ||
| entity = model.Entity("entity", model.EntityType.CO_MANAGED_ENTITY, statement=[f]) | ||
| sm = self._submodel(entity) | ||
| files: List[model.File] = [e for e in walk_submodel(sm) if isinstance(e, model.File)] | ||
| self.assertEqual([f], files) | ||
|
|
||
| def test_file_inside_operation_variable_is_found(self): | ||
| """Regression test for issue #423: File inside Operation variable must be yielded.""" | ||
| f = model.File("file", content_type="application/pdf", value="/some/file.pdf") | ||
| op = model.Operation("op", input_variable=[f]) | ||
| sm = self._submodel(op) | ||
| files: List[model.File] = [e for e in walk_submodel(sm) if isinstance(e, model.File)] | ||
| self.assertEqual([f], files) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would keep this code in the
walk_submodel()method.I don't really see a use case for reusal, which would outweigh the annoyance to jump from one method to the other just to understand what's happening.
I'm free to be convinced otherwise though ;)