From efefd8a74dff225aa9b72c4450a2bd4d83ed5e93 Mon Sep 17 00:00:00 2001 From: Daniel Klein Date: Tue, 1 Sep 2026 08:40:42 +0000 Subject: [PATCH 1/5] Refactor pagination classes to use singular 'result' instead of plural 'results' for consistency with JSON Signed-off-by: Daniel Klein --- aas_http_client/classes/wrapper/pagination.py | 42 ++++++++-------- tests/test_wrapper.py | 48 +++++++++---------- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/aas_http_client/classes/wrapper/pagination.py b/aas_http_client/classes/wrapper/pagination.py index e113e78e..25f46526 100644 --- a/aas_http_client/classes/wrapper/pagination.py +++ b/aas_http_client/classes/wrapper/pagination.py @@ -26,39 +26,39 @@ class ShellPaginatedData: """Class representing paginated data for Asset Administration Shells.""" paging_metadata: PagingMetadata - results: list[model.AssetAdministrationShell] + result: list[model.AssetAdministrationShell] - def __init__(self, cursor: str, results: list[model.AssetAdministrationShell]) -> None: + def __init__(self, cursor: str, result: list[model.AssetAdministrationShell]) -> None: """Initialize a paginated data object. :param paging_metadata: Paging metadata :param results: list of results """ self.paging_metadata = PagingMetadata(cursor) - self.results = results + self.result = result class ReferencePaginatedData: """Class representing paginated data for References.""" paging_metadata: PagingMetadata - results: list[model.ModelReference] + result: list[model.ModelReference] - def __init__(self, cursor: str, results: list[model.ModelReference]) -> None: + def __init__(self, cursor: str, result: list[model.ModelReference]) -> None: """Initialize a paginated data object. :param paging_metadata: Paging metadata :param results: list of results """ self.paging_metadata = PagingMetadata(cursor) - self.results = results + self.result = result class SubmodelPaginatedData: """Class representing paginated data for Submodels.""" paging_metadata: PagingMetadata - results: list[model.Submodel] + result: list[model.Submodel] def __init__(self, cursor: str, results: list[model.Submodel]) -> None: """Initialize a paginated data object. @@ -67,30 +67,30 @@ def __init__(self, cursor: str, results: list[model.Submodel]) -> None: :param results: list of results """ self.paging_metadata = PagingMetadata(cursor) - self.results = results + self.result = results class SubmodelElementPaginatedData: """Class representing paginated data for Submodel Elements.""" paging_metadata: PagingMetadata - results: list[model.SubmodelElement] + result: list[model.SubmodelElement] - def __init__(self, cursor: str, results: list[model.SubmodelElement]) -> None: + def __init__(self, cursor: str, result: list[model.SubmodelElement]) -> None: """Initialize a paginated data object. :param paging_metadata: Paging metadata :param results: list of results """ self.paging_metadata = PagingMetadata(cursor) - self.results = results + self.result = result class ShellDescriptorPaginatedData: """Class representing paginated data for Shell Descriptors.""" paging_metadata: PagingMetadata - results: list[model.SubmodelElement] + result: list[model.SubmodelElement] def __init__(self, cursor: str, results: list[model.SubmodelElement]) -> None: """Initialize a paginated data object. @@ -99,7 +99,7 @@ def __init__(self, cursor: str, results: list[model.SubmodelElement]) -> None: :param results: list of results """ self.paging_metadata = PagingMetadata(cursor) - self.results = results + self.result = results def create_shell_paging_data(content: dict) -> ShellPaginatedData | None: @@ -113,7 +113,7 @@ def create_shell_paging_data(content: dict) -> ShellPaginatedData | None: results: list = content.get("result", []) if not results or len(results) == 0: _logger.warning("No shells found on server.") - return ShellPaginatedData(cursor="", results=[]) + return ShellPaginatedData(cursor="", result=[]) for result in results: if not isinstance(result, dict): @@ -133,7 +133,7 @@ def create_shell_paging_data(content: dict) -> ShellPaginatedData | None: return ShellPaginatedData( cursor=cursor, - results=aas_list, + result=aas_list, ) @@ -183,7 +183,7 @@ def create_submodel_element_paging_data(content: dict) -> SubmodelElementPaginat results: list = content.get("result", []) if not results or len(results) == 0: _logger.warning("No shells found on server.") - return SubmodelElementPaginatedData(cursor="", results=[]) + return SubmodelElementPaginatedData(cursor="", result=[]) for result in results: if not isinstance(result, dict): @@ -203,7 +203,7 @@ def create_submodel_element_paging_data(content: dict) -> SubmodelElementPaginat return SubmodelElementPaginatedData( cursor=cursor, - results=sme_list, + result=sme_list, ) @@ -218,7 +218,7 @@ def create_shell_descriptor_paging_data(content: dict) -> SubmodelElementPaginat results: list = content.get("result", []) if not results or len(results) == 0: _logger.warning("No shells found on server.") - return SubmodelElementPaginatedData(cursor="", results=[]) + return SubmodelElementPaginatedData(cursor="", result=[]) for result in results: if not isinstance(result, dict): @@ -238,7 +238,7 @@ def create_shell_descriptor_paging_data(content: dict) -> SubmodelElementPaginat return SubmodelElementPaginatedData( cursor=cursor, - results=sme_list, + result=sme_list, ) @@ -253,7 +253,7 @@ def create_reference_paging_data(content: dict) -> ReferencePaginatedData | None results: list = content.get("result", []) if not results or len(results) == 0: _logger.warning("No shells found on server.") - return ReferencePaginatedData(cursor="", results=[]) + return ReferencePaginatedData(cursor="", result=[]) for result in results: if not isinstance(result, dict): @@ -273,5 +273,5 @@ def create_reference_paging_data(content: dict) -> ReferencePaginatedData | None return ReferencePaginatedData( cursor=cursor, - results=ref_list, + result=ref_list, ) diff --git a/tests/test_wrapper.py b/tests/test_wrapper.py index 3816591f..d427dde5 100644 --- a/tests/test_wrapper.py +++ b/tests/test_wrapper.py @@ -119,7 +119,7 @@ def test_001b_delete_all_asset_administration_shells(wrapper: SdkWrapper): result = wrapper.get_all_asset_administration_shells() assert result is not None - for shell in result.results: + for shell in result.result: shell_id = shell.id if wrapper.get_encoded_ids() == IdEncoding.encoded: @@ -130,13 +130,13 @@ def test_001b_delete_all_asset_administration_shells(wrapper: SdkWrapper): assert delete_result shells_result = wrapper.get_all_asset_administration_shells() - assert len(shells_result.results) == 0 + assert len(shells_result.result) == 0 def test_001c_delete_all_submodels(wrapper: SdkWrapper): result = wrapper.get_all_submodels() assert result is not None - for submodel in result.results: + for submodel in result.result: submodel_id = submodel.id if wrapper.get_encoded_ids() == IdEncoding.encoded: @@ -147,12 +147,12 @@ def test_001c_delete_all_submodels(wrapper: SdkWrapper): assert delete_result submodels_result = wrapper.get_all_submodels() - assert len(submodels_result.results) == 0 + assert len(submodels_result.result) == 0 def test_002_get_all_asset_administration_shells(wrapper: SdkWrapper): shells = wrapper.get_all_asset_administration_shells() assert shells is not None - assert len(shells.results) == 0 + assert len(shells.result) == 0 def test_003_post_asset_administration_shell(wrapper: SdkWrapper, shared_aas: model.AssetAdministrationShell): shell = wrapper.post_asset_administration_shell(shared_aas) @@ -163,9 +163,9 @@ def test_003_post_asset_administration_shell(wrapper: SdkWrapper, shared_aas: mo shells = wrapper.get_all_asset_administration_shells() assert shells is not None - assert len(shells.results) == 1 - assert shells.results[0].id_short == shared_aas.id_short - assert shells.results[0].id == SHELL_ID + assert len(shells.result) == 1 + assert shells.result[0].id_short == shared_aas.id_short + assert shells.result[0].id == SHELL_ID def test_004a_get_asset_administration_shell_by_id(wrapper: SdkWrapper, shared_aas: model.AssetAdministrationShell): shell_id = SHELL_ID @@ -273,7 +273,7 @@ def test_007_get_submodel_by_id_aas_repository(wrapper: SdkWrapper, shared_sm: m def test_008_get_all_submodels(wrapper: SdkWrapper): submodels = wrapper.get_all_submodels() assert submodels is not None - assert len(submodels.results) == 0 + assert len(submodels.result) == 0 def test_009a_post_submodel(wrapper: SdkWrapper, shared_sm: model.Submodel): submodel = wrapper.post_submodel(shared_sm) @@ -284,9 +284,9 @@ def test_009a_post_submodel(wrapper: SdkWrapper, shared_sm: model.Submodel): submodels = wrapper.get_all_submodels() assert submodels is not None - assert len(submodels.results) == 1 - assert submodels.results[0].id_short == shared_sm.id_short - assert submodels.results[0].id == SM_ID + assert len(submodels.result) == 1 + assert submodels.result[0].id_short == shared_sm.id_short + assert submodels.result[0].id == SM_ID def test_009b_post_submodel(wrapper: SdkWrapper): sm_template_file = Path(f"./tests/test_data/aimc.json").resolve() @@ -303,7 +303,7 @@ def test_009b_post_submodel(wrapper: SdkWrapper): get_result = wrapper.get_all_submodels() assert get_result is not None - submodels = get_result.results + submodels = get_result.result assert len(submodels) == 2 def test_010_get_submodel_by_id_aas_repository(wrapper: SdkWrapper, shared_sm: model.Submodel): @@ -479,7 +479,7 @@ def test_015_get_all_submodel_elements_submodel_repository(wrapper: SdkWrapper): submodel_elements = wrapper.get_all_submodel_elements_submodel_repository(sm_id) assert submodel_elements is not None - assert len(submodel_elements.results) == 0 + assert len(submodel_elements.result) == 0 def test_016a_post_submodel_element_submodel_repo(wrapper: SdkWrapper, shared_sme_string: model.Property): sm_id = SM_ID @@ -503,7 +503,7 @@ def test_016a_post_submodel_element_submodel_repo(wrapper: SdkWrapper, shared_sm submodel_elements = wrapper.get_all_submodel_elements_submodel_repository(sm_id) assert submodel_elements is not None - assert len(submodel_elements.results) == 1 + assert len(submodel_elements.result) == 1 def test_016b_post_submodel_element_submodel_repo(wrapper: SdkWrapper, shared_sme_bool: model.Property): sm_id = SM_ID @@ -527,7 +527,7 @@ def test_016b_post_submodel_element_submodel_repo(wrapper: SdkWrapper, shared_sm submodel_elements = wrapper.get_all_submodel_elements_submodel_repository(sm_id) assert submodel_elements is not None - assert len(submodel_elements.results) == 2 + assert len(submodel_elements.result) == 2 def test_016c_post_submodel_element_submodel_repo(wrapper: SdkWrapper, shared_sme_int: model.Property): sm_id = SM_ID @@ -551,7 +551,7 @@ def test_016c_post_submodel_element_submodel_repo(wrapper: SdkWrapper, shared_sm submodel_elements = wrapper.get_all_submodel_elements_submodel_repository(sm_id) assert submodel_elements is not None - assert len(submodel_elements.results) == 3 + assert len(submodel_elements.result) == 3 def test_016d_post_submodel_element_submodel_repo(wrapper: SdkWrapper, shared_sme_float: model.Property): sm_id = SM_ID @@ -575,7 +575,7 @@ def test_016d_post_submodel_element_submodel_repo(wrapper: SdkWrapper, shared_sm submodel_elements = wrapper.get_all_submodel_elements_submodel_repository(sm_id) assert submodel_elements is not None - assert len(submodel_elements.results) == 4 + assert len(submodel_elements.result) == 4 def test_017a_get_submodel_element_by_path_submodel_repo(wrapper: SdkWrapper, shared_sme_string: model.Property): sm_id = SM_ID @@ -1054,7 +1054,7 @@ def test_029_get_all_submodel_references_aas_repository(wrapper: SdkWrapper): result = wrapper.get_all_submodel_references_aas_repository(shell_id) assert result is not None - references = result.results + references = result.result assert len(references) == 1 def test_030_post_submodel_reference_aas_repository(wrapper: SdkWrapper): @@ -1077,7 +1077,7 @@ def test_030_post_submodel_reference_aas_repository(wrapper: SdkWrapper): check_result = wrapper.get_all_submodel_references_aas_repository(shell_id) assert check_result is not None - assert len(check_result.results) == 2 + assert len(check_result.result) == 2 def test_031_delete_submodel_reference_by_id_aas_repository(wrapper: SdkWrapper): shell_id = SHELL_ID @@ -1093,7 +1093,7 @@ def test_031_delete_submodel_reference_by_id_aas_repository(wrapper: SdkWrapper) get_result = wrapper.get_all_submodel_references_aas_repository(shell_id) assert get_result is not None - assert len(get_result.results) == 1 + assert len(get_result.result) == 1 def test_032_put_submodel_element_by_path_submodel_repo(wrapper: SdkWrapper, shared_sme_string: model.Property): sm_id = SM_ID @@ -1314,7 +1314,7 @@ def test_098_delete_asset_administration_shell_by_id(wrapper: SdkWrapper): shells = wrapper.get_all_asset_administration_shells() assert shells is not None - assert len(shells.results) == 0 + assert len(shells.result) == 0 def test_099a_delete_submodel_by_id(wrapper: SdkWrapper): sm_id = SM_ID @@ -1328,7 +1328,7 @@ def test_099a_delete_submodel_by_id(wrapper: SdkWrapper): submodels = wrapper.get_all_submodels() assert submodels is not None - assert len(submodels.results) == 1 + assert len(submodels.result) == 1 def test_099b_delete_submodel_by_id(wrapper: SdkWrapper): sm_id = SM_ID @@ -1342,4 +1342,4 @@ def test_099b_delete_submodel_by_id(wrapper: SdkWrapper): submodels = wrapper.get_all_submodels() assert submodels is not None - assert len(submodels.results) == 0 + assert len(submodels.result) == 0 From 6ed6a486e18829a705ff545ab20bf97cb147e672 Mon Sep 17 00:00:00 2001 From: Daniel Klein Date: Tue, 1 Sep 2026 08:41:16 +0000 Subject: [PATCH 2/5] Add pull_policy to multiple services in docker-compose.yaml for consistency Signed-off-by: Daniel Klein --- .devcontainer/docker-compose.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.devcontainer/docker-compose.yaml b/.devcontainer/docker-compose.yaml index 224c5432..8e74fb1e 100644 --- a/.devcontainer/docker-compose.yaml +++ b/.devcontainer/docker-compose.yaml @@ -2,6 +2,7 @@ services: dotnet_repo_server: image: engineeringmethodsag/fluid-basyx-dotnet-repo-server:latest container_name: dotnetaasserver + pull_policy: always networks: - bridge ports: @@ -10,6 +11,7 @@ services: python_aas_server: image: engineeringmethodsag/fluid-basyx-python-aasx-server:latest container_name: pythonaasserver + pull_policy: always networks: - bridge ports: @@ -113,6 +115,7 @@ services: go_aas_configuration: container_name: aas_basyx_configuration image: eclipsebasyx/basyxconfigurationservice-go:latest + pull_policy: always environment: - POSTGRES_HOST=postgres - POSTGRES_PORT=5432 @@ -132,6 +135,7 @@ services: go_aas_environment: container_name: goaasserver image: eclipsebasyx/aasenvironment-go:latest + pull_policy: always command: ["/app/aasenvironmentservice"] ports: - "8070:8082" From b58038b8a66dab9b6b0c9953294496a97995e41a Mon Sep 17 00:00:00 2001 From: Daniel Klein Date: Tue, 1 Sep 2026 08:41:31 +0000 Subject: [PATCH 3/5] Add create_embedded_data_specification function to generate embedded data specifications Signed-off-by: Daniel Klein --- aas_http_client/utilities/model_builder.py | 59 ++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/aas_http_client/utilities/model_builder.py b/aas_http_client/utilities/model_builder.py index f14b8398..8848a68d 100644 --- a/aas_http_client/utilities/model_builder.py +++ b/aas_http_client/utilities/model_builder.py @@ -169,3 +169,62 @@ def create_reference(id: str) -> model.ModelReference: :return: ModelReference instance """ return model.ModelReference.from_referable(model.Submodel(id)) + + +def create_embedded_data_specification( + preferred_name: model.PreferredNameTypeIEC61360, + definition: model.DefinitionTypeIEC61360 | None, + short_name: model.ShortNameTypeIEC61360 | None, + unit: str | None, + unit_id: model.Reference | None, + data_type: model.DataTypeIEC61360 | None, + source_of_definition: str | None, + symbol: str | None, + value_format: str | None, + value_list: set[model.ValueReferencePair] | None, + value: str | None, + level_types: list[model.IEC61360LevelType] | None, +) -> model.EmbeddedDataSpecification | None: + """Create an embedded data specification with the given parameters. + + :param preferred_name: The preferred name of the concept description. + :param definition: The definition of the concept description. + :param short_name: The short name of the concept description. + :param unit: The unit of the concept description. + :param unit_id: The unit reference of the concept description. + :param data_type: The data type of the concept description. + :param source_of_definition: The source of definition of the concept description. + :param symbol: The symbol of the concept description. + :param value_format: The value format of the concept description. + :param value_list: The value list of the concept description. + :param value: The value of the concept description. + :param level_types: The level types of the concept description. + :return: An instance of EmbeddedDataSpecification if successful, None otherwise. + """ + if not preferred_name: + _logger.warning("Preferred name is missing for the embedded data specification.") + return None + + return model.EmbeddedDataSpecification( + data_specification=model.ExternalReference( + ( + model.Key( + type_=model.KeyTypes.GLOBAL_REFERENCE, value="https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIEC61360/3/1" + ), + ) + ), + data_specification_content=model.DataSpecificationIEC61360( + preferred_name=preferred_name, + definition=definition, + short_name=short_name, + unit=unit, + unit_id=unit_id, + data_type=data_type, + source_of_definition=source_of_definition, + symbol=symbol, + value_format=value_format, + value_list=value_list, + value=value, + level_types=level_types or (), + ), + ) From 89af89f591d74b187dbcc419f3a4b854d1cdeeb4 Mon Sep 17 00:00:00 2001 From: Daniel Klein Date: Tue, 1 Sep 2026 08:50:18 +0000 Subject: [PATCH 4/5] Update dependencies in pyproject.toml and requirements.txt for consistency and version upgrades Signed-off-by: Daniel Klein --- pyproject.toml | 7 +++---- requirements.txt | 17 ++++++++++------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8e0ed9fa..6bd2fb2b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,11 +11,10 @@ license = { file = "LICENSE" } authors = [{ name = "Daniel Klein", email = "daniel.klein@em.ag" }] requires-python = ">=3.13" dependencies = [ - "pydantic>=2.13.4", + "pydantic>=2.13.5", "requests>=2.34.2", - "basyx-python-sdk>=2.0.1", - "puremagic==1.30", - "types-requests>=2.33.0.20260518", + "basyx-python-sdk==2.1.0", + "puremagic==2.2.0", ] [project.urls] Homepage = "https://github.com/fluid40/aas-http-client" diff --git a/requirements.txt b/requirements.txt index c0145f70..5ee73e3f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,15 @@ -pydantic>=2.13.4 +# Runtime dependencies +pydantic>=2.13.5 requests>=2.34.2 -pytest>=9.1.1 -pytest-cov>=7.1.0 -build>=1.5.0 -python-json-logger>=4.1.0 -pre_commit>=4.6.2 -commitizen>=4.17.0 basyx-python-sdk==2.1.0 puremagic==2.2.0 + +# Development and tooling +pytest>=9.1.1 +pytest-cov>=7.1.0 +python-json-logger>=4.2.0 +build>=1.6.0 +pre-commit>=4.6.2 +commitizen>=4.18.0 types-requests>=2.33.0.20260712 From 0d1f7b1f377d8fdbb30c1c0b3a9c42b3316f5c35 Mon Sep 17 00:00:00 2001 From: Daniel Klein Date: Tue, 1 Sep 2026 08:53:15 +0000 Subject: [PATCH 5/5] Bump version to 1.2.9 and update changelog with recent changes Signed-off-by: Daniel Klein --- docs/CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d14aca67..d2940e7e 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,5 +1,11 @@ # 📝 Changelog +## [1.2.9] - 2026-09-01 + +* 🧹Chore: Refactor wrapper pagination classes to use singular 'result' instead of plural 'results' for consistency with JSON. +* ✨Feat: Add create_embedded_data_specification function to model builder. +* 🚀Improvement: Update dependencies in pyproject.toml and requirements.txt for consistency and version upgrades + ## [1.2.8] - 2026-08-26 * 🚀Improvement: Add delete concept descriptions functions to experimental implementations. diff --git a/pyproject.toml b/pyproject.toml index 6bd2fb2b..f1a1f863 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "aas-http-client" -version = "1.2.8" +version = "1.2.9" description = "Flexible Python HTTP client for communication with various types of AAS servers." readme = "README.md" license = { file = "LICENSE" }