From 855693e0ef90311c497299af3b80ab7fc97edad2 Mon Sep 17 00:00:00 2001
From: Igor Garmaev <56840636+zrgt@users.noreply.github.com>
Date: Wed, 13 May 2026 15:05:20 +0200
Subject: [PATCH 01/12] fix: align ServiceSpecificationProfileEnum with
IDTA-01002 v3.1.2 spec (#541)
- Fix SUBMODEL_READ/SUBMODEL_VALUE names (were swapped: SSP-002=Read, SSP-003=Value)
- Rename AAS_REPOSITORY_BULK -> AAS_REPOSITORY_QUERY (SSP-003 is Query, no Bulk exists)
- Rename SUBMODEL_REPOSITORY_BULK -> SUBMODEL_REPOSITORY_TEMPLATE (SSP-003 is Template)
- Rename CONCEPT_DESCRIPTION_REPOSITORY_READ -> CONCEPT_DESCRIPTION_REPOSITORY_QUERY (SSP-002)
- Remove CONCEPT_DESCRIPTION_REPOSITORY_BULK (SSP-003 does not exist in spec)
- Add AAS_REGISTRY_QUERY (SSP-004), AAS_REGISTRY_MINIMAL_READ (SSP-005)
- Add SUBMODEL_REGISTRY_QUERY (SSP-004)
- Add SUBMODEL_REPOSITORY_TEMPLATE_READ (SSP-004), SUBMODEL_REPOSITORY_QUERY (SSP-005)
---
server/app/model/service_specification.py | 27 ++++++++++++++++-------
1 file changed, 19 insertions(+), 8 deletions(-)
diff --git a/server/app/model/service_specification.py b/server/app/model/service_specification.py
index 00b4a5da..5181901a 100644
--- a/server/app/model/service_specification.py
+++ b/server/app/model/service_specification.py
@@ -5,8 +5,11 @@
class ServiceSpecificationProfileEnum(str, enum.Enum):
"""
Enumeration of all standardized Service Specification Profiles
- from the AAS Part 2 API Specification (IDTA-01002-3-1).
+ from the AAS Part 2 API Specification (IDTA-01002-3-1-2).
Each profile is uniquely identified by its semantic URI.
+
+ Reference: https://industrialdigitaltwin.io/aas-specifications/IDTA-01002/v3.1.2/
+ http-rest-api/service-specifications-and-profiles.html
"""
# --- Asset Administration Shell (AAS) ---
@@ -15,8 +18,8 @@ class ServiceSpecificationProfileEnum(str, enum.Enum):
# --- Submodel ---
SUBMODEL_FULL = "https://admin-shell.io/aas/API/3/1/SubmodelServiceSpecification/SSP-001"
- SUBMODEL_VALUE = "https://admin-shell.io/aas/API/3/1/SubmodelServiceSpecification/SSP-002"
- SUBMODEL_READ = "https://admin-shell.io/aas/API/3/1/SubmodelServiceSpecification/SSP-003"
+ SUBMODEL_READ = "https://admin-shell.io/aas/API/3/1/SubmodelServiceSpecification/SSP-002"
+ SUBMODEL_VALUE = "https://admin-shell.io/aas/API/3/1/SubmodelServiceSpecification/SSP-003"
# --- AASX File Server ---
AASX_FILESERVER_FULL = "https://admin-shell.io/aas/API/3/1/AasxFileServerServiceSpecification/SSP-001"
@@ -28,32 +31,40 @@ class ServiceSpecificationProfileEnum(str, enum.Enum):
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-002"
AAS_REGISTRY_BULK = \
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-003"
+ AAS_REGISTRY_QUERY = \
+ "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-004"
+ AAS_REGISTRY_MINIMAL_READ = \
+ "https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRegistryServiceSpecification/SSP-005"
# --- Submodel Registry ---
SUBMODEL_REGISTRY_FULL = "https://admin-shell.io/aas/API/3/1/SubmodelRegistryServiceSpecification/SSP-001"
SUBMODEL_REGISTRY_READ = "https://admin-shell.io/aas/API/3/1/SubmodelRegistryServiceSpecification/SSP-002"
SUBMODEL_REGISTRY_BULK = "https://admin-shell.io/aas/API/3/1/SubmodelRegistryServiceSpecification/SSP-003"
+ SUBMODEL_REGISTRY_QUERY = "https://admin-shell.io/aas/API/3/1/SubmodelRegistryServiceSpecification/SSP-004"
# --- AAS Repository ---
AAS_REPOSITORY_FULL = \
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRepositoryServiceSpecification/SSP-001"
AAS_REPOSITORY_READ = \
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRepositoryServiceSpecification/SSP-002"
- AAS_REPOSITORY_BULK = \
+ AAS_REPOSITORY_QUERY = \
"https://admin-shell.io/aas/API/3/1/AssetAdministrationShellRepositoryServiceSpecification/SSP-003"
# --- Submodel Repository ---
SUBMODEL_REPOSITORY_FULL = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-001"
SUBMODEL_REPOSITORY_READ = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-002"
- SUBMODEL_REPOSITORY_BULK = "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-003"
+ SUBMODEL_REPOSITORY_TEMPLATE = \
+ "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-003"
+ SUBMODEL_REPOSITORY_TEMPLATE_READ = \
+ "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-004"
+ SUBMODEL_REPOSITORY_QUERY = \
+ "https://admin-shell.io/aas/API/3/1/SubmodelRepositoryServiceSpecification/SSP-005"
# --- Concept Description Repository ---
CONCEPT_DESCRIPTION_REPOSITORY_FULL = \
"https://admin-shell.io/aas/API/3/1/ConceptDescriptionRepositoryServiceSpecification/SSP-001"
- CONCEPT_DESCRIPTION_REPOSITORY_READ = \
+ CONCEPT_DESCRIPTION_REPOSITORY_QUERY = \
"https://admin-shell.io/aas/API/3/1/ConceptDescriptionRepositoryServiceSpecification/SSP-002"
- CONCEPT_DESCRIPTION_REPOSITORY_BULK = \
- "https://admin-shell.io/aas/API/3/1/ConceptDescriptionRepositoryServiceSpecification/SSP-003"
# --- Discovery ---
DISCOVERY_FULL = "https://admin-shell.io/aas/API/3/1/DiscoveryServiceSpecification/SSP-001"
From cd39f2ea7540b955bedac9d98329028e2ecba4d2 Mon Sep 17 00:00:00 2001
From: Igor Garmaev <56840636+zrgt@users.noreply.github.com>
Date: Wed, 13 May 2026 15:15:02 +0200
Subject: [PATCH 02/12] fix: NamespaceSet.pop() removes item from all backends
(#514)
pop() only removed item from first backend via popitem(), leaving stale
entries in remaining backends and causing false AASConstraintViolation
on subsequent add() for same semantic_id.
Fixes #496
---
sdk/basyx/aas/model/base.py | 4 +++-
sdk/test/model/test_base.py | 12 ++++++++++++
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/sdk/basyx/aas/model/base.py b/sdk/basyx/aas/model/base.py
index 6c6eb25e..718c0d63 100644
--- a/sdk/basyx/aas/model/base.py
+++ b/sdk/basyx/aas/model/base.py
@@ -2077,8 +2077,10 @@ def discard(self, x: _NSO) -> None:
def pop(self) -> _NSO:
_, value = next(iter(self._backend.values()))[0].popitem()
+ for key_attr_name, (backend_dict, case_sensitive) in self._backend.items():
+ key_attr_value = self._get_attribute(value, key_attr_name, case_sensitive)
+ backend_dict.pop(key_attr_value, None)
self._execute_item_del_hook(value)
- value.parent = None
return value
def clear(self) -> None:
diff --git a/sdk/test/model/test_base.py b/sdk/test/model/test_base.py
index c5b0429d..3a74a774 100644
--- a/sdk/test/model/test_base.py
+++ b/sdk/test/model/test_base.py
@@ -338,6 +338,18 @@ def setUp(self):
self.namespace = self._namespace_class()
self.namespace3 = self._namespace_class_qualifier()
+ def test_namespaceset_pop_removes_from_all_backends(self) -> None:
+ # set1 has two backends: id_short and semantic_id
+ self.namespace.set1.add(self.prop1)
+ popped = self.namespace.set1.pop()
+ self.assertIs(self.prop1, popped)
+ self.assertEqual(0, len(self.namespace.set1))
+ # After pop, adding a new item with the same semantic_id must NOT raise AASConstraintViolation —
+ # it would if the popped item's semantic_id entry were still in the backend
+ new_prop = model.Property("NewProp", model.datatypes.Int, semantic_id=self.propSemanticID)
+ self.namespace.set1.add(new_prop)
+ self.assertEqual(1, len(self.namespace.set1))
+
def test_NamespaceSet(self) -> None:
self.namespace.set1.add(self.prop1)
self.assertEqual(1, len(self.namespace.set1))
From 34a6d6f68d7851601ca8943ee4a2c222022b8a94 Mon Sep 17 00:00:00 2001
From: Igor Garmaev <56840636+zrgt@users.noreply.github.com>
Date: Thu, 14 May 2026 13:18:14 +0200
Subject: [PATCH 03/12] Fix load_directory silently dropping all descriptors
(#545)
Previously `load_directory()` called `read_server_aas_json_file_into()`, which internally only adds items where `isinstance(item, model.Identifiable)`. `AssetAdministrationShellDescriptor` and `SubmodelDescriptor` are not `Identifiable`, so all descriptors were silently skipped. The registry always started empty.
This parses descriptor JSON directly with `ServerAASFromJsonDecoder` and add items to `DictDescriptorStore`.
Fixes #544
---
server/app/adapter/jsonization.py | 27 +++----------------
server/app/model/provider.py | 44 +++++++++++++++++--------------
2 files changed, 27 insertions(+), 44 deletions(-)
diff --git a/server/app/adapter/jsonization.py b/server/app/adapter/jsonization.py
index a8ee3471..897590c7 100644
--- a/server/app/adapter/jsonization.py
+++ b/server/app/adapter/jsonization.py
@@ -1,10 +1,10 @@
import logging
-from typing import Callable, Dict, Optional, Set, Type
+from typing import Callable, Dict, Type
from basyx.aas import model
-from basyx.aas.adapter._generic import ASSET_KIND, ASSET_KIND_INVERSE, JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES, PathOrIO
+from basyx.aas.adapter._generic import ASSET_KIND, ASSET_KIND_INVERSE, JSON_AAS_TOP_LEVEL_KEYS_TO_TYPES
from basyx.aas.adapter.json import AASToJsonEncoder
-from basyx.aas.adapter.json.json_deserialization import AASFromJsonDecoder, _get_ts, read_aas_json_file_into
+from basyx.aas.adapter.json.json_deserialization import AASFromJsonDecoder, _get_ts
import app.model as server_model
@@ -207,27 +207,6 @@ class ServerStrictStrippedAASFromJsonDecoder(ServerStrictAASFromJsonDecoder, Ser
pass
-def read_server_aas_json_file_into(
- object_store: model.AbstractObjectStore,
- file: PathOrIO,
- replace_existing: bool = False,
- ignore_existing: bool = False,
- failsafe: bool = True,
- stripped: bool = False,
- decoder: Optional[Type[AASFromJsonDecoder]] = None,
-) -> Set[model.Identifier]:
- return read_aas_json_file_into(
- object_store=object_store,
- file=file,
- replace_existing=replace_existing,
- ignore_existing=ignore_existing,
- failsafe=failsafe,
- stripped=stripped,
- decoder=decoder,
- keys_to_types=JSON_SERVER_AAS_TOP_LEVEL_KEYS_TO_TYPES,
- )
-
-
class ServerAASToJsonEncoder(AASToJsonEncoder):
@classmethod
diff --git a/server/app/model/provider.py b/server/app/model/provider.py
index 97067e7d..409570fe 100644
--- a/server/app/model/provider.py
+++ b/server/app/model/provider.py
@@ -1,10 +1,11 @@
+import json
from pathlib import Path
from typing import IO, Dict, Iterable, Iterator, Union
from basyx.aas import model
from basyx.aas.model import provider as sdk_provider
-import app.adapter as adapter
+from app.adapter import ServerAASFromJsonDecoder
from app.model import descriptor
PathOrIO = Union[Path, IO]
@@ -53,27 +54,30 @@ def __iter__(self) -> Iterator[_DESCRIPTOR_TYPE]:
def load_directory(directory: Union[Path, str]) -> DictDescriptorStore:
"""
- Create a new :class:`~basyx.aas.model.provider.DictIdentifiableStore` and use it to load Asset Administration Shell
- and Submodel files in ``AASX``, ``JSON`` and ``XML`` format from a given directory into memory. Additionally, load
- all embedded supplementary files into a new :class:`~basyx.aas.adapter.aasx.DictSupplementaryFileContainer`.
-
- :param directory: :class:`~pathlib.Path` or ``str`` pointing to the directory containing all Asset Administration
- Shell and Submodel files to load
- :return: Tuple consisting of a :class:`~basyx.aas.model.provider.DictIdentifiableStore` and a
- :class:`~basyx.aas.adapter.aasx.DictSupplementaryFileContainer` containing all loaded data
- """
-
- dict_descriptor_store: DictDescriptorStore = DictDescriptorStore()
+ Load AAS/Submodel descriptor JSON files from a directory into a :class:`DictDescriptorStore`.
+ :param directory: Path to the directory containing JSON descriptor files
+ :return: Populated :class:`DictDescriptorStore`
+ """
+ store = DictDescriptorStore()
directory = Path(directory)
for file in directory.iterdir():
- if not file.is_file():
+ if not file.is_file() or file.suffix.lower() != ".json":
continue
-
- suffix = file.suffix.lower()
- if suffix == ".json":
- with open(file) as f:
- adapter.read_server_aas_json_file_into(dict_descriptor_store, f)
-
- return dict_descriptor_store
+ with open(file) as f:
+ data = json.load(f, cls=ServerAASFromJsonDecoder)
+ for item in data.get("assetAdministrationShellDescriptors", []):
+ if isinstance(item, descriptor.AssetAdministrationShellDescriptor):
+ try:
+ store.add(item)
+ except KeyError:
+ pass
+ for item in data.get("submodelDescriptors", []):
+ if isinstance(item, descriptor.SubmodelDescriptor):
+ try:
+ store.add(item)
+ except KeyError:
+ pass
+
+ return store
From ba01ebf21992fcfd42ab1759974b1bd3a953616c Mon Sep 17 00:00:00 2001
From: Igor Garmaev <56840636+zrgt@users.noreply.github.com>
Date: Thu, 14 May 2026 13:59:43 +0200
Subject: [PATCH 04/12] Fix LocalFileIdentifiableStore: count and iterate only
.json store files (#507)
Previously, `LocalFileIdentifiableStore` counted any file in it's storage directory for its `__len__`.
Now it only counts `.json` files. Additional unittests ensure that the file ending used in `__len__` fits with the way files are written in the store.
Fixes #499
Fixes #503
---
sdk/basyx/aas/backend/local_file.py | 5 +++--
sdk/test/backend/test_local_file.py | 27 +++++++++++++++++++++++++++
2 files changed, 30 insertions(+), 2 deletions(-)
diff --git a/sdk/basyx/aas/backend/local_file.py b/sdk/basyx/aas/backend/local_file.py
index 4008497a..72d5605a 100644
--- a/sdk/basyx/aas/backend/local_file.py
+++ b/sdk/basyx/aas/backend/local_file.py
@@ -150,7 +150,7 @@ def __len__(self) -> int:
:return: The number of objects (determined from the number of documents)
"""
logger.debug("Fetching number of documents from database ...")
- return len(os.listdir(self.directory_path))
+ return sum(1 for f in os.listdir(self.directory_path) if f.lower().endswith(".json"))
def __iter__(self) -> Iterator[model.Identifiable]:
"""
@@ -161,7 +161,8 @@ def __iter__(self) -> Iterator[model.Identifiable]:
"""
logger.debug("Iterating over objects in database ...")
for name in os.listdir(self.directory_path):
- yield self.get_identifiable_by_hash(name.rstrip(".json"))
+ if name.lower().endswith(".json"):
+ yield self.get_identifiable_by_hash(name[:-5])
@staticmethod
def _transform_id(identifier: model.Identifier) -> str:
diff --git a/sdk/test/backend/test_local_file.py b/sdk/test/backend/test_local_file.py
index adcbfcc7..f1080240 100644
--- a/sdk/test/backend/test_local_file.py
+++ b/sdk/test/backend/test_local_file.py
@@ -107,6 +107,33 @@ def test_key_errors(self) -> None:
self.assertEqual("'No AAS object with id https://example.org/Test_Submodel exists in "
"local file database'", str(cm.exception))
+ def test_add_and_len_consistent(self) -> None:
+ # Each add() must increment len() by exactly 1
+ example_data = list(create_full_example())
+ for i, item in enumerate(example_data):
+ self.identifiable_store.add(item)
+ self.assertEqual(i + 1, len(self.identifiable_store))
+
+ # Stray non-json file must not be counted
+ stray = os.path.join(store_path, ".DS_Store")
+ with open(stray, "w") as f:
+ f.write("stray")
+ self.assertEqual(len(example_data), len(self.identifiable_store))
+ os.remove(stray)
+
+ def test_iter_ignores_non_json_files(self) -> None:
+ example_data = create_full_example()
+ for item in example_data:
+ self.identifiable_store.add(item)
+
+ # Stray files must not crash the iterator or be yielded
+ stray = os.path.join(store_path, ".DS_Store")
+ with open(stray, "w") as f:
+ f.write("stray")
+ items = list(self.identifiable_store)
+ self.assertEqual(5, len(items))
+ os.remove(stray)
+
def test_reload_discard(self) -> None:
# Load example submodel
example_submodel = create_example_submodel()
From be31bd0a98f2141632f7bf77a4effa98e721ba23 Mon Sep 17 00:00:00 2001
From: Igor Garmaev <56840636+zrgt@users.noreply.github.com>
Date: Thu, 14 May 2026 14:20:20 +0200
Subject: [PATCH 05/12] fix: XML DataSpecificationIEC61360.value independent of
value_format (#510)
Previously, `DataSpecificationIEC61360.value` was dropped silently, if `DataSpecificationIEC61360.value_format` was `None` in the XML deserialization. There's no constraint (anymore) that enforces this.
Therefore, we change the code to deserialize `value` independent of `value_format`.
Fixes #501
---
.../aas/adapter/xml/xml_deserialization.py | 2 +-
.../adapter/xml/test_xml_deserialization.py | 44 +++++++++++++++++++
2 files changed, 45 insertions(+), 1 deletion(-)
diff --git a/sdk/basyx/aas/adapter/xml/xml_deserialization.py b/sdk/basyx/aas/adapter/xml/xml_deserialization.py
index b36dddb9..2330b9af 100644
--- a/sdk/basyx/aas/adapter/xml/xml_deserialization.py
+++ b/sdk/basyx/aas/adapter/xml/xml_deserialization.py
@@ -1158,7 +1158,7 @@ def construct_data_specification_iec61360(cls, element: etree._Element,
if value_list is not None:
ds_iec.value_list = value_list
value = _get_text_or_none(element.find(NS_AAS + "value"))
- if value is not None and value_format is not None:
+ if value is not None:
ds_iec.value = value
level_type = element.find(NS_AAS + "levelType")
if level_type is not None:
diff --git a/sdk/test/adapter/xml/test_xml_deserialization.py b/sdk/test/adapter/xml/test_xml_deserialization.py
index 2857a9dc..14a5041b 100644
--- a/sdk/test/adapter/xml/test_xml_deserialization.py
+++ b/sdk/test/adapter/xml/test_xml_deserialization.py
@@ -428,6 +428,50 @@ def test_stripped_asset_administration_shell(self) -> None:
self.assertEqual(len(aas.submodel), 0)
+class XmlDeserializationDataSpecTest(unittest.TestCase):
+ def test_data_spec_iec61360_value_without_value_format(self) -> None:
+ xml = _xml_wrap(f"""
+
+
+ http://example.org/test_cd
+
+
+
+ ExternalReference
+
+
+ GlobalReference
+ https://admin-shell.io/DataSpecificationTemplates/DataSpecificationIec61360/3/0
+
+
+
+
+
+
+
+ en
+ Test
+
+
+ test_value
+
+
+
+
+
+
+ """)
+ object_store = read_aas_xml_file(io.StringIO(xml), failsafe=False)
+ cd = object_store.get_item("http://example.org/test_cd")
+ self.assertIsInstance(cd, model.ConceptDescription)
+ assert isinstance(cd, model.ConceptDescription)
+ ds_content = list(cd.embedded_data_specifications)[0].data_specification_content
+ self.assertIsInstance(ds_content, model.DataSpecificationIEC61360)
+ assert isinstance(ds_content, model.DataSpecificationIEC61360)
+ self.assertEqual("test_value", ds_content.value)
+ self.assertIsNone(ds_content.value_format)
+
+
class XmlDeserializationDerivingTest(unittest.TestCase):
def test_submodel_constructor_overriding(self) -> None:
class EnhancedSubmodel(model.Submodel):
From ca78e9a92e4383914065bdf11c7e3841d449ea36 Mon Sep 17 00:00:00 2001
From: Igor Garmaev <56840636+zrgt@users.noreply.github.com>
Date: Thu, 14 May 2026 14:31:27 +0200
Subject: [PATCH 06/12] fix: DictSupplementaryFileContainer increment refcount
in _assign_unique_name (#513)
`DictSupplementaryFileContainer_store_refcount` is designed to track how many `_name_map` entries reference the same content hash, so `delete_file()` can free the underlying bytes only when the last reference is removed. The refcount is never incremented, so files are never freed.
Previously, because `_assign_unique_name()` never increments `_store_refcount`, the count stays at 0 after any `add_file()`. Every `delete_file()` decrements to -1 and the equality check `== 0` is never true, so `_store[hash]` and `_store_refcount[hash]` are never cleaned up. Every file ever added leaks indefinitely.
This fixes this bug by incrementing `_store_refcount[sha] += 1` inside `_assign_unique_name()` when a new `_name_map` entry is created (the first branch of the `while True` loop). Also decrement it (and skip the increment) inside the second branch when a duplicate name already maps to the same hash.
Fixes #495
---
sdk/basyx/aas/adapter/aasx.py | 2 ++
sdk/test/adapter/aasx/test_aasx.py | 18 ++++++++++++++++++
2 files changed, 20 insertions(+)
diff --git a/sdk/basyx/aas/adapter/aasx.py b/sdk/basyx/aas/adapter/aasx.py
index 2c7fe0b4..82fe4b76 100644
--- a/sdk/basyx/aas/adapter/aasx.py
+++ b/sdk/basyx/aas/adapter/aasx.py
@@ -880,6 +880,7 @@ def rename_file(self, old_name: str, new_name: str) -> str:
if new_name == old_name:
return new_name
file_hash, file_content_type = self._name_map[old_name]
+ self._store_refcount[file_hash] -= 1
del self._name_map[old_name]
return self._assign_unique_name(new_name, file_hash, file_content_type)
@@ -889,6 +890,7 @@ def _assign_unique_name(self, name: str, sha: bytes, content_type: str) -> str:
while True:
if new_name not in self._name_map:
self._name_map[new_name] = (sha, content_type)
+ self._store_refcount[sha] += 1
return new_name
elif self._name_map[new_name] == (sha, content_type):
return new_name
diff --git a/sdk/test/adapter/aasx/test_aasx.py b/sdk/test/adapter/aasx/test_aasx.py
index 271b992c..e4ab1a1d 100644
--- a/sdk/test/adapter/aasx/test_aasx.py
+++ b/sdk/test/adapter/aasx/test_aasx.py
@@ -89,6 +89,24 @@ def test_supplementary_file_container(self) -> None:
with self.assertRaises(KeyError):
container.write_file(duplicate_file, file_content)
+ def test_supplementary_file_container_refcount(self) -> None:
+ container = aasx.DictSupplementaryFileContainer()
+ data = b"test content"
+ name1 = container.add_file("/file1.bin", io.BytesIO(data), "application/octet-stream")
+ name2 = container.add_file("/file2.bin", io.BytesIO(data), "application/octet-stream")
+ content_hash = container.get_sha256(name1)
+
+ # Both names point to same content — backing store must be present
+ self.assertIn(content_hash, container._store)
+
+ # Deleting one reference must NOT free the backing store
+ container.delete_file(name1)
+ self.assertIn(content_hash, container._store)
+
+ # Deleting the last reference must free the backing store
+ container.delete_file(name2)
+ self.assertNotIn(content_hash, container._store)
+
class AASXWriterTest(unittest.TestCase):
def test_write_missing_aas_objects(self):
From 5535d537b0a37e4a459d95275ebc1212fdedb883 Mon Sep 17 00:00:00 2001
From: Igor Garmaev <56840636+zrgt@users.noreply.github.com>
Date: Mon, 18 May 2026 14:45:37 +0200
Subject: [PATCH 07/12] Fix GET /shells?assetIds: multiple globalAssetId values
silently returned empty results (#512)
When 2 or more `globalAssetId` query parameters were sent, a
`len(global_asset_ids) <= 1` guard in the filter lambda in
`repository.py` evaluated `False` for every shell, causing an
empty HTTP 200 response with no error.
The guard was likely intended to reject invalid input with a 400
error, not silently discard all results.
The guard is replaced with proper input validation that raises
`BadRequest` when multiple global asset IDs are provided.
Fixes #500
---
server/app/interfaces/repository.py | 5 +----
.../test/interfaces/test_shells_asset_ids.py | 20 +++++++++++++++++--
2 files changed, 19 insertions(+), 6 deletions(-)
diff --git a/server/app/interfaces/repository.py b/server/app/interfaces/repository.py
index 0e75eedd..89ad0d64 100644
--- a/server/app/interfaces/repository.py
+++ b/server/app/interfaces/repository.py
@@ -470,10 +470,7 @@ def _get_shells(
for specific_asset_id in specific_asset_ids
)
)
- and (
- len(global_asset_ids) <= 1
- and (not global_asset_ids or shell.asset_information.global_asset_id in global_asset_ids)
- )
+ and (not global_asset_ids or shell.asset_information.global_asset_id in global_asset_ids)
),
aas,
)
diff --git a/server/test/interfaces/test_shells_asset_ids.py b/server/test/interfaces/test_shells_asset_ids.py
index 8b48d6de..da103807 100644
--- a/server/test/interfaces/test_shells_asset_ids.py
+++ b/server/test/interfaces/test_shells_asset_ids.py
@@ -16,6 +16,8 @@
from app.interfaces.repository import WSGIApp
+BASE_PATH = "/api/v3.1"
+
def _encode_asset_id(name: str, value: str) -> str:
payload = json.dumps({"name": name, "value": value})
@@ -24,10 +26,24 @@ def _encode_asset_id(name: str, value: str) -> str:
class ShellsAssetIdsTest(unittest.TestCase):
def setUp(self) -> None:
- app = WSGIApp(create_full_example(), DictSupplementaryFileContainer())
+ self.example_data = create_full_example()
+ app = WSGIApp(self.example_data, DictSupplementaryFileContainer())
self.client = Client(app)
+ def test_multiple_global_asset_ids_returns_matching_results(self) -> None:
+ aas_list = [obj for obj in self.example_data if isinstance(obj, model.AssetAdministrationShell)]
+ known_id = aas_list[0].asset_information.global_asset_id
+ assert known_id is not None
+ unknown_id = "http://example.org/nonexistent_asset"
+ id1 = _encode_asset_id("globalAssetId", known_id)
+ id2 = _encode_asset_id("globalAssetId", unknown_id)
+ response = self.client.get(f"{BASE_PATH}/shells?assetIds={id1}&assetIds={id2}")
+ self.assertEqual(200, response.status_code)
+ result = json.loads(response.data)
+ returned_ids = [r["id"] for r in result]
+ self.assertIn(aas_list[0].id, returned_ids)
+
def test_malformed_asset_id_missing_field_returns_400(self) -> None:
bad_payload = base64.urlsafe_b64encode(b'{"name": "globalAssetId"}').decode()
- response = self.client.get(f"/api/v3.1/shells?assetIds={bad_payload}")
+ response = self.client.get(f"{BASE_PATH}/shells?assetIds={bad_payload}")
self.assertEqual(400, response.status_code)
From 2b3716a85c5a45006623c74fe42865077701ecb6 Mon Sep 17 00:00:00 2001
From: Henri Poeche
Date: Mon, 18 May 2026 14:49:20 +0200
Subject: [PATCH 08/12] compliance_tool: remove unused schema check (#549)
The compliance tool still had code to check a json or xml file
against the defined schema from admin-shell-io/aas-specs-metamodel.
The option to run this schema check was removed earlier from cli.py
with commit af73a4b.
As no other code uses the implemented schema check, this function
is now removed, including the schema files and the unittests.
---
.../compliance_check_aasx.py | 63 -
.../compliance_check_json.py | 78 -
.../compliance_check_xml.py | 77 -
.../schemas/aasJSONSchema.json | 1528 -----------------
.../schemas/aasXMLSchema.xsd | 1344 ---------------
.../test/test_compliance_check_json.py | 44 -
.../test/test_compliance_check_xml.py | 35 -
7 files changed, 3169 deletions(-)
delete mode 100644 compliance_tool/aas_compliance_tool/schemas/aasJSONSchema.json
delete mode 100644 compliance_tool/aas_compliance_tool/schemas/aasXMLSchema.xsd
diff --git a/compliance_tool/aas_compliance_tool/compliance_check_aasx.py b/compliance_tool/aas_compliance_tool/compliance_check_aasx.py
index 0b10f5fe..40c4f2cd 100644
--- a/compliance_tool/aas_compliance_tool/compliance_check_aasx.py
+++ b/compliance_tool/aas_compliance_tool/compliance_check_aasx.py
@@ -88,69 +88,6 @@ def check_deserialization(file_path: str, state_manager: ComplianceToolStateMana
return identifiable_store, files, new_cp
-def check_schema(file_path: str, state_manager: ComplianceToolStateManager) -> None:
- """
- Checks a given file against the official json schema and reports any issues using the given
- :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager`
-
- Opens the file and checks if the data inside is stored in XML or JSON. Then calls the respective compliance tool
- schema check
- """
- logger = logging.getLogger('compliance_check')
- logger.addHandler(state_manager)
- logger.propagate = False
- logger.setLevel(logging.INFO)
-
- # create handler to get logger info
- logger_deserialization = logging.getLogger(aasx.__name__)
- logger_deserialization.addHandler(state_manager)
- logger_deserialization.propagate = False
- logger_deserialization.setLevel(logging.INFO)
-
- state_manager.add_step('Open file')
- try:
- # open given file
- reader = aasx.AASXReader(file_path)
- state_manager.set_step_status_from_log()
- except ValueError as error:
- logger.error(error)
- state_manager.set_step_status_from_log()
- state_manager.add_step('Read file')
- state_manager.set_step_status(Status.NOT_EXECUTED)
- return
-
- try:
- # read given file (Find XML and JSON parts)
- state_manager.add_step('Read file')
- core_rels = reader.reader.get_related_parts_by_type()
- try:
- aasx_origin_part = core_rels[aasx.RELATIONSHIP_TYPE_AASX_ORIGIN][0]
- except IndexError as e:
- raise ValueError("Not a valid AASX file: aasx-origin Relationship is missing.") from e
- state_manager.set_step_status(Status.SUCCESS)
- for aas_part in reader.reader.get_related_parts_by_type(aasx_origin_part)[
- aasx.RELATIONSHIP_TYPE_AAS_SPEC]:
- content_type = reader.reader.get_content_type(aas_part)
- extension = aas_part.split("/")[-1].split(".")[-1]
- with reader.reader.open_part(aas_part) as p:
- if content_type.split(";")[0] in (
- "text/xml", "application/xml") or content_type == "" and extension == "xml":
- logger.debug("Parsing AAS objects from XML stream in OPC part {} ...".format(aas_part))
- compliance_check_xml._check_schema(p, state_manager)
- elif content_type.split(";")[0] == "application/json" \
- or content_type == "" and extension == "json":
- logger.debug("Parsing AAS objects from JSON stream in OPC part {} ...".format(aas_part))
- compliance_check_json._check_schema(io.TextIOWrapper(p, encoding='utf-8-sig'), state_manager)
- else:
- raise ValueError("Could not determine part format of AASX part {} (Content Type: {}, extension: {}"
- .format(aas_part, content_type, extension))
- except ValueError as error:
- logger.error(error)
- state_manager.set_step_status(Status.FAILED)
- finally:
- reader.close()
-
-
def check_aas_example(file_path: str, state_manager: ComplianceToolStateManager, **kwargs) -> None:
"""
Checks if a file contains all elements of the aas example and reports any issues using the given
diff --git a/compliance_tool/aas_compliance_tool/compliance_check_json.py b/compliance_tool/aas_compliance_tool/compliance_check_json.py
index b021fa96..e50332ec 100644
--- a/compliance_tool/aas_compliance_tool/compliance_check_json.py
+++ b/compliance_tool/aas_compliance_tool/compliance_check_json.py
@@ -23,84 +23,6 @@
from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status
-JSON_SCHEMA_FILE = os.path.join(os.path.dirname(__file__), 'schemas/aasJSONSchema.json')
-
-
-def check_schema(file_path: str, state_manager: ComplianceToolStateManager) -> None:
- """
- Checks a given file against the official json schema and reports any issues using the given
- :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager`
-
- Add the steps: `Open file`, `Read file and check if it is conform to the json syntax` and `Validate file against
- official json schema`
-
- :param file_path: Path to the file which should be checked
- :param state_manager: :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager` to log the steps
- """
- logger = logging.getLogger('compliance_check')
- logger.addHandler(state_manager)
- logger.propagate = False
- logger.setLevel(logging.INFO)
-
- state_manager.add_step('Open file')
- try:
- # open given file
- file_to_be_checked = open(file_path, 'r', encoding='utf-8-sig')
- except IOError as error:
- state_manager.set_step_status(Status.FAILED)
- logger.error(error)
- state_manager.add_step('Read file and check if it is conform to the json syntax')
- state_manager.set_step_status(Status.NOT_EXECUTED)
- state_manager.add_step('Validate file against official json schema')
- state_manager.set_step_status(Status.NOT_EXECUTED)
- return
- return _check_schema(file_to_be_checked, state_manager)
-
-
-def _check_schema(file_to_be_checked: IO[str], state_manager: ComplianceToolStateManager):
- logger = logging.getLogger('compliance_check')
- logger.addHandler(state_manager)
- logger.propagate = False
- logger.setLevel(logging.INFO)
-
- try:
- with file_to_be_checked:
- state_manager.set_step_status(Status.SUCCESS)
- # read given file and check if it is conform to the json syntax
- state_manager.add_step('Read file and check if it is conform to the json syntax')
- json_to_be_checked = json.load(file_to_be_checked)
- state_manager.set_step_status(Status.SUCCESS)
- except json.decoder.JSONDecodeError as error:
- state_manager.set_step_status(Status.FAILED)
- logger.error(error)
- state_manager.add_step('Validate file against official json schema')
- state_manager.set_step_status(Status.NOT_EXECUTED)
- return
-
- # load json schema
- with open(JSON_SCHEMA_FILE, 'r', encoding='utf-8-sig') as json_file:
- aas_json_schema = json.load(json_file)
- state_manager.add_step('Validate file against official json schema')
-
- # validate given file against schema
- try:
- import jsonschema # type: ignore
- except ImportError as error:
- state_manager.set_step_status(Status.NOT_EXECUTED)
- logger.error("Python package 'jsonschema' is required for validating the JSON file.", error)
- return
-
- try:
- jsonschema.validate(instance=json_to_be_checked, schema=aas_json_schema)
- except jsonschema.exceptions.ValidationError as error:
- state_manager.set_step_status(Status.FAILED)
- logger.error(error)
- return
-
- state_manager.set_step_status(Status.SUCCESS)
- return
-
-
def check_deserialization(file_path: str, state_manager: ComplianceToolStateManager,
file_info: Optional[str] = None) -> model.DictIdentifiableStore:
"""
diff --git a/compliance_tool/aas_compliance_tool/compliance_check_xml.py b/compliance_tool/aas_compliance_tool/compliance_check_xml.py
index 81f2b5ff..eeb9924c 100644
--- a/compliance_tool/aas_compliance_tool/compliance_check_xml.py
+++ b/compliance_tool/aas_compliance_tool/compliance_check_xml.py
@@ -23,83 +23,6 @@
from aas_compliance_tool.state_manager import ComplianceToolStateManager, Status
-XML_SCHEMA_FILE = os.path.join(os.path.dirname(__file__), 'schemas/aasXMLSchema.xsd')
-
-
-def check_schema(file_path: str, state_manager: ComplianceToolStateManager) -> None:
- """
- Checks a given file against the official xml schema and reports any issues using the given
- :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager`
-
- Add the steps: `Open file`, `Read file`, `Check if it is conform to the xml syntax` and `Validate file against
- official xml schema`
-
- :param file_path: Path to the file which should be checked
- :param state_manager: :class:`~basyx.aas.compliance_tool.state_manager.ComplianceToolStateManager` to log the steps
- """
- logger = logging.getLogger('compliance_check')
- logger.addHandler(state_manager)
- logger.propagate = False
- logger.setLevel(logging.INFO)
-
- state_manager.add_step('Open file')
- try:
- # open given file
- file_to_be_checked = open(file_path, 'rb')
- state_manager.set_step_status(Status.SUCCESS)
- except IOError as error:
- state_manager.set_step_status(Status.FAILED)
- logger.error(error)
- state_manager.add_step('Read file and check if it is conform to the xml syntax')
- state_manager.set_step_status(Status.NOT_EXECUTED)
- state_manager.add_step('Validate file against official xml schema')
- state_manager.set_step_status(Status.NOT_EXECUTED)
- return
- return _check_schema(file_to_be_checked, state_manager)
-
-
-def _check_schema(file_to_be_checked, state_manager):
- logger = logging.getLogger('compliance_check')
- logger.addHandler(state_manager)
- logger.propagate = False
- logger.setLevel(logging.INFO)
-
- state_manager.add_step('Read file and check if it is conform to the xml syntax')
- try:
- # read given file and check if it is conform to the xml syntax
- parser = etree.XMLParser(remove_blank_text=True, remove_comments=True)
- etree.parse(file_to_be_checked, parser)
- state_manager.set_step_status(Status.SUCCESS)
- except etree.XMLSyntaxError as error:
- state_manager.set_step_status(Status.FAILED)
- logger.error(error)
- state_manager.add_step('Validate file against official xml schema')
- state_manager.set_step_status(Status.NOT_EXECUTED)
- file_to_be_checked.close()
- return
- except Exception:
- file_to_be_checked.close()
- raise
-
- # load aas xml schema
- aas_xml_schema = etree.XMLSchema(file=XML_SCHEMA_FILE)
- parser = etree.XMLParser(schema=aas_xml_schema)
-
- state_manager.add_step('Validate file against official xml schema')
- # validate given file against schema
- try:
- file_to_be_checked.seek(0) # Reset reading file offset (cursor) to the beginning of the file
- with file_to_be_checked:
- etree.parse(file_to_be_checked, parser=parser)
- except etree.ParseError as error:
- state_manager.set_step_status(Status.FAILED)
- logger.error(error)
- return
-
- state_manager.set_step_status(Status.SUCCESS)
- return
-
-
def check_deserialization(file_path: str, state_manager: ComplianceToolStateManager,
file_info: Optional[str] = None) -> model.DictIdentifiableStore:
"""
diff --git a/compliance_tool/aas_compliance_tool/schemas/aasJSONSchema.json b/compliance_tool/aas_compliance_tool/schemas/aasJSONSchema.json
deleted file mode 100644
index 7ba1a360..00000000
--- a/compliance_tool/aas_compliance_tool/schemas/aasJSONSchema.json
+++ /dev/null
@@ -1,1528 +0,0 @@
-{
- "$schema": "https://json-schema.org/draft/2019-09/schema",
- "title": "AssetAdministrationShellEnvironment",
- "type": "object",
- "allOf": [
- {
- "$ref": "#/definitions/Environment"
- }
- ],
- "$id": "https://admin-shell.io/aas/3/1",
- "definitions": {
- "AasSubmodelElements": {
- "type": "string",
- "enum": [
- "AnnotatedRelationshipElement",
- "BasicEventElement",
- "Blob",
- "Capability",
- "DataElement",
- "Entity",
- "EventElement",
- "File",
- "MultiLanguageProperty",
- "Operation",
- "Property",
- "Range",
- "ReferenceElement",
- "RelationshipElement",
- "SubmodelElement",
- "SubmodelElementCollection",
- "SubmodelElementList"
- ]
- },
- "AbstractLangString": {
- "type": "object",
- "properties": {
- "language": {
- "type": "string",
- "pattern": "^(([a-zA-Z]{2,3}(-[a-zA-Z]{3}(-[a-zA-Z]{3}){2})?|[a-zA-Z]{4}|[a-zA-Z]{5,8})(-[a-zA-Z]{4})?(-([a-zA-Z]{2}|[0-9]{3}))?(-(([a-zA-Z0-9]){5,8}|[0-9]([a-zA-Z0-9]){3}))*(-[0-9A-WY-Za-wy-z](-([a-zA-Z0-9]){2,8})+)*(-[xX](-([a-zA-Z0-9]){1,8})+)?|[xX](-([a-zA-Z0-9]){1,8})+|((en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)|(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)))$"
- },
- "text": {
- "type": "string",
- "minLength": 1,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- }
- },
- "required": [
- "language",
- "text"
- ]
- },
- "AdministrativeInformation": {
- "allOf": [
- {
- "$ref": "#/definitions/HasDataSpecification"
- },
- {
- "properties": {
- "version": {
- "type": "string",
- "allOf": [
- {
- "minLength": 1,
- "maxLength": 4
- },
- {
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- {
- "pattern": "^(0|[1-9][0-9]*)$"
- }
- ]
- },
- "revision": {
- "type": "string",
- "allOf": [
- {
- "minLength": 1,
- "maxLength": 4
- },
- {
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- {
- "pattern": "^(0|[1-9][0-9]*)$"
- }
- ]
- },
- "creator": {
- "$ref": "#/definitions/Reference"
- },
- "templateId": {
- "type": "string",
- "minLength": 1,
- "maxLength": 2000,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- }
- }
- }
- ]
- },
- "AnnotatedRelationshipElement": {
- "allOf": [
- {
- "$ref": "#/definitions/RelationshipElement_abstract"
- },
- {
- "properties": {
- "annotations": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/DataElement_choice"
- },
- "minItems": 1
- },
- "modelType": {
- "const": "AnnotatedRelationshipElement"
- }
- }
- }
- ]
- },
- "AssetAdministrationShell": {
- "allOf": [
- {
- "$ref": "#/definitions/Identifiable"
- },
- {
- "$ref": "#/definitions/HasDataSpecification"
- },
- {
- "properties": {
- "derivedFrom": {
- "$ref": "#/definitions/Reference"
- },
- "assetInformation": {
- "$ref": "#/definitions/AssetInformation"
- },
- "submodels": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/Reference"
- },
- "minItems": 1
- },
- "modelType": {
- "const": "AssetAdministrationShell"
- }
- },
- "required": [
- "assetInformation"
- ]
- }
- ]
- },
- "AssetInformation": {
- "type": "object",
- "properties": {
- "assetKind": {
- "$ref": "#/definitions/AssetKind"
- },
- "globalAssetId": {
- "type": "string",
- "minLength": 1,
- "maxLength": 2000,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- "specificAssetIds": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/SpecificAssetId"
- },
- "minItems": 1
- },
- "assetType": {
- "type": "string",
- "minLength": 1,
- "maxLength": 2000,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- "defaultThumbnail": {
- "$ref": "#/definitions/Resource"
- }
- },
- "required": [
- "assetKind"
- ]
- },
- "AssetKind": {
- "type": "string",
- "enum": [
- "Instance",
- "NotApplicable",
- "Type"
- ]
- },
- "BasicEventElement": {
- "allOf": [
- {
- "$ref": "#/definitions/EventElement"
- },
- {
- "properties": {
- "observed": {
- "$ref": "#/definitions/Reference"
- },
- "direction": {
- "$ref": "#/definitions/Direction"
- },
- "state": {
- "$ref": "#/definitions/StateOfEvent"
- },
- "messageTopic": {
- "type": "string",
- "minLength": 1,
- "maxLength": 255,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- "messageBroker": {
- "$ref": "#/definitions/Reference"
- },
- "lastUpdate": {
- "type": "string",
- "pattern": "^-?(([1-9][0-9][0-9][0-9]+)|(0[0-9][0-9][0-9]))-((0[1-9])|(1[0-2]))-((0[1-9])|([12][0-9])|(3[01]))T(((([01][0-9])|(2[0-3])):[0-5][0-9]:([0-5][0-9])(\\.[0-9]+)?)|24:00:00(\\.0+)?)(Z|\\+00:00|-00:00)$"
- },
- "minInterval": {
- "type": "string",
- "pattern": "^-?P((([0-9]+Y([0-9]+M)?([0-9]+D)?|([0-9]+M)([0-9]+D)?|([0-9]+D))(T(([0-9]+H)([0-9]+M)?([0-9]+(\\.[0-9]+)?S)?|([0-9]+M)([0-9]+(\\.[0-9]+)?S)?|([0-9]+(\\.[0-9]+)?S)))?)|(T(([0-9]+H)([0-9]+M)?([0-9]+(\\.[0-9]+)?S)?|([0-9]+M)([0-9]+(\\.[0-9]+)?S)?|([0-9]+(\\.[0-9]+)?S))))$"
- },
- "maxInterval": {
- "type": "string",
- "pattern": "^-?P((([0-9]+Y([0-9]+M)?([0-9]+D)?|([0-9]+M)([0-9]+D)?|([0-9]+D))(T(([0-9]+H)([0-9]+M)?([0-9]+(\\.[0-9]+)?S)?|([0-9]+M)([0-9]+(\\.[0-9]+)?S)?|([0-9]+(\\.[0-9]+)?S)))?)|(T(([0-9]+H)([0-9]+M)?([0-9]+(\\.[0-9]+)?S)?|([0-9]+M)([0-9]+(\\.[0-9]+)?S)?|([0-9]+(\\.[0-9]+)?S))))$"
- },
- "modelType": {
- "const": "BasicEventElement"
- }
- },
- "required": [
- "observed",
- "direction",
- "state"
- ]
- }
- ]
- },
- "Blob": {
- "allOf": [
- {
- "$ref": "#/definitions/DataElement"
- },
- {
- "properties": {
- "value": {
- "type": "string",
- "contentEncoding": "base64"
- },
- "contentType": {
- "type": "string",
- "allOf": [
- {
- "minLength": 1,
- "maxLength": 100
- },
- {
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- {
- "pattern": "^([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+/([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+([ \\t]*;[ \\t]*([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+=(([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+|\"(([\\t !#-\\[\\]-~]|[\u0080-\u00ff])|\\\\([\\t !-~]|[\u0080-\u00ff]))*\"))*$"
- }
- ]
- },
- "modelType": {
- "const": "Blob"
- }
- },
- "required": [
- "contentType"
- ]
- }
- ]
- },
- "Capability": {
- "allOf": [
- {
- "$ref": "#/definitions/SubmodelElement"
- },
- {
- "properties": {
- "modelType": {
- "const": "Capability"
- }
- }
- }
- ]
- },
- "ConceptDescription": {
- "allOf": [
- {
- "$ref": "#/definitions/Identifiable"
- },
- {
- "$ref": "#/definitions/HasDataSpecification"
- },
- {
- "properties": {
- "isCaseOf": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/Reference"
- },
- "minItems": 1
- },
- "modelType": {
- "const": "ConceptDescription"
- }
- }
- }
- ]
- },
- "DataElement": {
- "$ref": "#/definitions/SubmodelElement"
- },
- "DataElement_choice": {
- "oneOf": [
- {
- "$ref": "#/definitions/Blob"
- },
- {
- "$ref": "#/definitions/File"
- },
- {
- "$ref": "#/definitions/MultiLanguageProperty"
- },
- {
- "$ref": "#/definitions/Property"
- },
- {
- "$ref": "#/definitions/Range"
- },
- {
- "$ref": "#/definitions/ReferenceElement"
- }
- ]
- },
- "DataSpecificationContent": {
- "type": "object",
- "properties": {
- "modelType": {
- "$ref": "#/definitions/ModelType"
- }
- },
- "required": [
- "modelType"
- ]
- },
- "DataSpecificationContent_choice": {
- "oneOf": [
- {
- "$ref": "#/definitions/DataSpecificationIec61360"
- }
- ]
- },
- "DataSpecificationIec61360": {
- "allOf": [
- {
- "$ref": "#/definitions/DataSpecificationContent"
- },
- {
- "properties": {
- "preferredName": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/LangStringPreferredNameTypeIec61360"
- },
- "minItems": 1
- },
- "shortName": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/LangStringShortNameTypeIec61360"
- },
- "minItems": 1
- },
- "unit": {
- "type": "string",
- "minLength": 1,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- "unitId": {
- "$ref": "#/definitions/Reference"
- },
- "sourceOfDefinition": {
- "type": "string",
- "minLength": 1,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- "symbol": {
- "type": "string",
- "minLength": 1,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- "dataType": {
- "$ref": "#/definitions/DataTypeIec61360"
- },
- "definition": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/LangStringDefinitionTypeIec61360"
- },
- "minItems": 1
- },
- "valueFormat": {
- "type": "string",
- "minLength": 1,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- "valueList": {
- "$ref": "#/definitions/ValueList"
- },
- "value": {
- "type": "string",
- "minLength": 1,
- "maxLength": 2000,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- "levelType": {
- "$ref": "#/definitions/LevelType"
- },
- "modelType": {
- "const": "DataSpecificationIec61360"
- }
- },
- "required": [
- "preferredName"
- ]
- }
- ]
- },
- "DataTypeDefXsd": {
- "type": "string",
- "enum": [
- "xs:anyURI",
- "xs:base64Binary",
- "xs:boolean",
- "xs:byte",
- "xs:date",
- "xs:dateTime",
- "xs:decimal",
- "xs:double",
- "xs:duration",
- "xs:float",
- "xs:gDay",
- "xs:gMonth",
- "xs:gMonthDay",
- "xs:gYear",
- "xs:gYearMonth",
- "xs:hexBinary",
- "xs:int",
- "xs:integer",
- "xs:long",
- "xs:negativeInteger",
- "xs:nonNegativeInteger",
- "xs:nonPositiveInteger",
- "xs:positiveInteger",
- "xs:short",
- "xs:string",
- "xs:time",
- "xs:unsignedByte",
- "xs:unsignedInt",
- "xs:unsignedLong",
- "xs:unsignedShort"
- ]
- },
- "DataTypeIec61360": {
- "type": "string",
- "enum": [
- "BLOB",
- "BOOLEAN",
- "DATE",
- "FILE",
- "HTML",
- "INTEGER_COUNT",
- "INTEGER_CURRENCY",
- "INTEGER_MEASURE",
- "IRDI",
- "IRI",
- "RATIONAL",
- "RATIONAL_MEASURE",
- "REAL_COUNT",
- "REAL_CURRENCY",
- "REAL_MEASURE",
- "STRING",
- "STRING_TRANSLATABLE",
- "TIME",
- "TIMESTAMP"
- ]
- },
- "Direction": {
- "type": "string",
- "enum": [
- "input",
- "output"
- ]
- },
- "EmbeddedDataSpecification": {
- "type": "object",
- "properties": {
- "dataSpecification": {
- "$ref": "#/definitions/Reference"
- },
- "dataSpecificationContent": {
- "$ref": "#/definitions/DataSpecificationContent_choice"
- }
- },
- "required": [
- "dataSpecification",
- "dataSpecificationContent"
- ]
- },
- "Entity": {
- "allOf": [
- {
- "$ref": "#/definitions/SubmodelElement"
- },
- {
- "properties": {
- "statements": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/SubmodelElement_choice"
- },
- "minItems": 1
- },
- "entityType": {
- "$ref": "#/definitions/EntityType"
- },
- "globalAssetId": {
- "type": "string",
- "minLength": 1,
- "maxLength": 2000,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- "specificAssetIds": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/SpecificAssetId"
- },
- "minItems": 1
- },
- "modelType": {
- "const": "Entity"
- }
- },
- "required": [
- "entityType"
- ]
- }
- ]
- },
- "EntityType": {
- "type": "string",
- "enum": [
- "CoManagedEntity",
- "SelfManagedEntity"
- ]
- },
- "Environment": {
- "type": "object",
- "properties": {
- "assetAdministrationShells": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/AssetAdministrationShell"
- },
- "minItems": 1
- },
- "submodels": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/Submodel"
- },
- "minItems": 1
- },
- "conceptDescriptions": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/ConceptDescription"
- },
- "minItems": 1
- }
- }
- },
- "EventElement": {
- "$ref": "#/definitions/SubmodelElement"
- },
- "EventPayload": {
- "type": "object",
- "properties": {
- "source": {
- "$ref": "#/definitions/Reference"
- },
- "sourceSemanticId": {
- "$ref": "#/definitions/Reference"
- },
- "observableReference": {
- "$ref": "#/definitions/Reference"
- },
- "observableSemanticId": {
- "$ref": "#/definitions/Reference"
- },
- "topic": {
- "type": "string",
- "minLength": 1,
- "maxLength": 255,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- "subjectId": {
- "$ref": "#/definitions/Reference"
- },
- "timeStamp": {
- "type": "string",
- "pattern": "^-?(([1-9][0-9][0-9][0-9]+)|(0[0-9][0-9][0-9]))-((0[1-9])|(1[0-2]))-((0[1-9])|([12][0-9])|(3[01]))T(((([01][0-9])|(2[0-3])):[0-5][0-9]:([0-5][0-9])(\\.[0-9]+)?)|24:00:00(\\.0+)?)(Z|\\+00:00|-00:00)$"
- },
- "payload": {
- "type": "string",
- "contentEncoding": "base64"
- }
- },
- "required": [
- "source",
- "observableReference",
- "timeStamp"
- ]
- },
- "Extension": {
- "allOf": [
- {
- "$ref": "#/definitions/HasSemantics"
- },
- {
- "properties": {
- "name": {
- "type": "string",
- "minLength": 1,
- "maxLength": 128,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- "valueType": {
- "$ref": "#/definitions/DataTypeDefXsd"
- },
- "value": {
- "type": "string"
- },
- "refersTo": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/Reference"
- },
- "minItems": 1
- }
- },
- "required": [
- "name"
- ]
- }
- ]
- },
- "File": {
- "allOf": [
- {
- "$ref": "#/definitions/DataElement"
- },
- {
- "properties": {
- "value": {
- "type": "string"
- },
- "contentType": {
- "type": "string",
- "allOf": [
- {
- "minLength": 1,
- "maxLength": 100
- },
- {
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- {
- "pattern": "^([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+/([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+([ \\t]*;[ \\t]*([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+=(([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+|\"(([\\t !#-\\[\\]-~]|[\u0080-\u00ff])|\\\\([\\t !-~]|[\u0080-\u00ff]))*\"))*$"
- }
- ]
- },
- "modelType": {
- "const": "File"
- }
- },
- "required": [
- "contentType"
- ]
- }
- ]
- },
- "HasDataSpecification": {
- "type": "object",
- "properties": {
- "embeddedDataSpecifications": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/EmbeddedDataSpecification"
- },
- "minItems": 1
- }
- }
- },
- "HasExtensions": {
- "type": "object",
- "properties": {
- "extensions": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/Extension"
- },
- "minItems": 1
- }
- }
- },
- "HasKind": {
- "type": "object",
- "properties": {
- "kind": {
- "$ref": "#/definitions/ModellingKind"
- }
- }
- },
- "HasSemantics": {
- "type": "object",
- "properties": {
- "semanticId": {
- "$ref": "#/definitions/Reference"
- },
- "supplementalSemanticIds": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/Reference"
- },
- "minItems": 1
- }
- }
- },
- "Identifiable": {
- "allOf": [
- {
- "$ref": "#/definitions/Referable"
- },
- {
- "properties": {
- "administration": {
- "$ref": "#/definitions/AdministrativeInformation"
- },
- "id": {
- "type": "string",
- "minLength": 1,
- "maxLength": 2000,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- }
- },
- "required": [
- "id"
- ]
- }
- ]
- },
- "Key": {
- "type": "object",
- "properties": {
- "type": {
- "$ref": "#/definitions/KeyTypes"
- },
- "value": {
- "type": "string",
- "minLength": 1,
- "maxLength": 2000,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- }
- },
- "required": [
- "type",
- "value"
- ]
- },
- "KeyTypes": {
- "type": "string",
- "enum": [
- "AnnotatedRelationshipElement",
- "AssetAdministrationShell",
- "BasicEventElement",
- "Blob",
- "Capability",
- "ConceptDescription",
- "DataElement",
- "Entity",
- "EventElement",
- "File",
- "FragmentReference",
- "GlobalReference",
- "Identifiable",
- "MultiLanguageProperty",
- "Operation",
- "Property",
- "Range",
- "Referable",
- "ReferenceElement",
- "RelationshipElement",
- "Submodel",
- "SubmodelElement",
- "SubmodelElementCollection",
- "SubmodelElementList"
- ]
- },
- "LangStringDefinitionTypeIec61360": {
- "allOf": [
- {
- "$ref": "#/definitions/AbstractLangString"
- },
- {
- "properties": {
- "text": {
- "maxLength": 1023
- }
- }
- }
- ]
- },
- "LangStringNameType": {
- "allOf": [
- {
- "$ref": "#/definitions/AbstractLangString"
- },
- {
- "properties": {
- "text": {
- "maxLength": 128
- }
- }
- }
- ]
- },
- "LangStringPreferredNameTypeIec61360": {
- "allOf": [
- {
- "$ref": "#/definitions/AbstractLangString"
- },
- {
- "properties": {
- "text": {
- "maxLength": 255
- }
- }
- }
- ]
- },
- "LangStringShortNameTypeIec61360": {
- "allOf": [
- {
- "$ref": "#/definitions/AbstractLangString"
- },
- {
- "properties": {
- "text": {
- "maxLength": 18
- }
- }
- }
- ]
- },
- "LangStringTextType": {
- "allOf": [
- {
- "$ref": "#/definitions/AbstractLangString"
- },
- {
- "properties": {
- "text": {
- "maxLength": 1023
- }
- }
- }
- ]
- },
- "LevelType": {
- "type": "object",
- "properties": {
- "min": {
- "type": "boolean"
- },
- "nom": {
- "type": "boolean"
- },
- "typ": {
- "type": "boolean"
- },
- "max": {
- "type": "boolean"
- }
- },
- "required": [
- "min",
- "nom",
- "typ",
- "max"
- ]
- },
- "ModelType": {
- "type": "string",
- "enum": [
- "AnnotatedRelationshipElement",
- "AssetAdministrationShell",
- "BasicEventElement",
- "Blob",
- "Capability",
- "ConceptDescription",
- "DataSpecificationIec61360",
- "Entity",
- "File",
- "MultiLanguageProperty",
- "Operation",
- "Property",
- "Range",
- "ReferenceElement",
- "RelationshipElement",
- "Submodel",
- "SubmodelElementCollection",
- "SubmodelElementList"
- ]
- },
- "ModellingKind": {
- "type": "string",
- "enum": [
- "Instance",
- "Template"
- ]
- },
- "MultiLanguageProperty": {
- "allOf": [
- {
- "$ref": "#/definitions/DataElement"
- },
- {
- "properties": {
- "value": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/LangStringTextType"
- },
- "minItems": 1
- },
- "valueId": {
- "$ref": "#/definitions/Reference"
- },
- "modelType": {
- "const": "MultiLanguageProperty"
- }
- }
- }
- ]
- },
- "Operation": {
- "allOf": [
- {
- "$ref": "#/definitions/SubmodelElement"
- },
- {
- "properties": {
- "inputVariables": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/OperationVariable"
- },
- "minItems": 1
- },
- "outputVariables": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/OperationVariable"
- },
- "minItems": 1
- },
- "inoutputVariables": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/OperationVariable"
- },
- "minItems": 1
- },
- "modelType": {
- "const": "Operation"
- }
- }
- }
- ]
- },
- "OperationVariable": {
- "type": "object",
- "properties": {
- "value": {
- "$ref": "#/definitions/SubmodelElement_choice"
- }
- },
- "required": [
- "value"
- ]
- },
- "Property": {
- "allOf": [
- {
- "$ref": "#/definitions/DataElement"
- },
- {
- "properties": {
- "valueType": {
- "$ref": "#/definitions/DataTypeDefXsd"
- },
- "value": {
- "type": "string"
- },
- "valueId": {
- "$ref": "#/definitions/Reference"
- },
- "modelType": {
- "const": "Property"
- }
- },
- "required": [
- "valueType"
- ]
- }
- ]
- },
- "Qualifiable": {
- "type": "object",
- "properties": {
- "qualifiers": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/Qualifier"
- },
- "minItems": 1
- },
- "modelType": {
- "$ref": "#/definitions/ModelType"
- }
- },
- "required": [
- "modelType"
- ]
- },
- "Qualifier": {
- "allOf": [
- {
- "$ref": "#/definitions/HasSemantics"
- },
- {
- "properties": {
- "kind": {
- "$ref": "#/definitions/QualifierKind"
- },
- "type": {
- "type": "string",
- "minLength": 1,
- "maxLength": 128,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- "valueType": {
- "$ref": "#/definitions/DataTypeDefXsd"
- },
- "value": {
- "type": "string"
- },
- "valueId": {
- "$ref": "#/definitions/Reference"
- }
- },
- "required": [
- "type",
- "valueType"
- ]
- }
- ]
- },
- "QualifierKind": {
- "type": "string",
- "enum": [
- "ConceptQualifier",
- "TemplateQualifier",
- "ValueQualifier"
- ]
- },
- "Range": {
- "allOf": [
- {
- "$ref": "#/definitions/DataElement"
- },
- {
- "properties": {
- "valueType": {
- "$ref": "#/definitions/DataTypeDefXsd"
- },
- "min": {
- "type": "string"
- },
- "max": {
- "type": "string"
- },
- "modelType": {
- "const": "Range"
- }
- },
- "required": [
- "valueType"
- ]
- }
- ]
- },
- "Referable": {
- "allOf": [
- {
- "$ref": "#/definitions/HasExtensions"
- },
- {
- "properties": {
- "category": {
- "type": "string",
- "minLength": 1,
- "maxLength": 128,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- "idShort": {
- "type": "string",
- "allOf": [
- {
- "minLength": 1,
- "maxLength": 128
- },
- {
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- {
- "pattern": "^[a-zA-Z][a-zA-Z0-9_]*$"
- }
- ]
- },
- "displayName": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/LangStringNameType"
- },
- "minItems": 1
- },
- "description": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/LangStringTextType"
- },
- "minItems": 1
- },
- "modelType": {
- "$ref": "#/definitions/ModelType"
- }
- },
- "required": [
- "modelType"
- ]
- }
- ]
- },
- "Reference": {
- "type": "object",
- "properties": {
- "type": {
- "$ref": "#/definitions/ReferenceTypes"
- },
- "referredSemanticId": {
- "$ref": "#/definitions/Reference"
- },
- "keys": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/Key"
- },
- "minItems": 1
- }
- },
- "required": [
- "type",
- "keys"
- ]
- },
- "ReferenceElement": {
- "allOf": [
- {
- "$ref": "#/definitions/DataElement"
- },
- {
- "properties": {
- "value": {
- "$ref": "#/definitions/Reference"
- },
- "modelType": {
- "const": "ReferenceElement"
- }
- }
- }
- ]
- },
- "ReferenceTypes": {
- "type": "string",
- "enum": [
- "ExternalReference",
- "ModelReference"
- ]
- },
- "RelationshipElement": {
- "allOf": [
- {
- "$ref": "#/definitions/RelationshipElement_abstract"
- },
- {
- "properties": {
- "modelType": {
- "const": "RelationshipElement"
- }
- }
- }
- ]
- },
- "RelationshipElement_abstract": {
- "allOf": [
- {
- "$ref": "#/definitions/SubmodelElement"
- },
- {
- "properties": {
- "first": {
- "$ref": "#/definitions/Reference"
- },
- "second": {
- "$ref": "#/definitions/Reference"
- }
- },
- "required": [
- "first",
- "second"
- ]
- }
- ]
- },
- "RelationshipElement_choice": {
- "oneOf": [
- {
- "$ref": "#/definitions/RelationshipElement"
- },
- {
- "$ref": "#/definitions/AnnotatedRelationshipElement"
- }
- ]
- },
- "Resource": {
- "type": "object",
- "properties": {
- "path": {
- "type": "string",
- "allOf": [
- {
- "minLength": 1,
- "maxLength": 2000
- },
- {
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- {
- "pattern": "^file:(//((localhost|(\\[((([0-9A-Fa-f]{1,4}:){6}([0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|::([0-9A-Fa-f]{1,4}:){5}([0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|([0-9A-Fa-f]{1,4})?::([0-9A-Fa-f]{1,4}:){4}([0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})?::([0-9A-Fa-f]{1,4}:){3}([0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(([0-9A-Fa-f]{1,4}:){2}[0-9A-Fa-f]{1,4})?::([0-9A-Fa-f]{1,4}:){2}([0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(([0-9A-Fa-f]{1,4}:){3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:([0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(([0-9A-Fa-f]{1,4}:){4}[0-9A-Fa-f]{1,4})?::([0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(([0-9A-Fa-f]{1,4}:){5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(([0-9A-Fa-f]{1,4}:){6}[0-9A-Fa-f]{1,4})?::)|[vV][0-9A-Fa-f]+\\.([a-zA-Z0-9\\-._~]|[!$&'()*+,;=]|:)+)\\]|([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])|([a-zA-Z0-9\\-._~]|%[0-9A-Fa-f][0-9A-Fa-f]|[!$&'()*+,;=])*)))?/((([a-zA-Z0-9\\-._~]|%[0-9A-Fa-f][0-9A-Fa-f]|[!$&'()*+,;=]|[:@]))+(/(([a-zA-Z0-9\\-._~]|%[0-9A-Fa-f][0-9A-Fa-f]|[!$&'()*+,;=]|[:@]))*)*)?|/((([a-zA-Z0-9\\-._~]|%[0-9A-Fa-f][0-9A-Fa-f]|[!$&'()*+,;=]|[:@]))+(/(([a-zA-Z0-9\\-._~]|%[0-9A-Fa-f][0-9A-Fa-f]|[!$&'()*+,;=]|[:@]))*)*)?)$"
- }
- ]
- },
- "contentType": {
- "type": "string",
- "allOf": [
- {
- "minLength": 1,
- "maxLength": 100
- },
- {
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- {
- "pattern": "^([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+/([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+([ \\t]*;[ \\t]*([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+=(([!#$%&'*+\\-.^_`|~0-9a-zA-Z])+|\"(([\\t !#-\\[\\]-~]|[\u0080-\u00ff])|\\\\([\\t !-~]|[\u0080-\u00ff]))*\"))*$"
- }
- ]
- }
- },
- "required": [
- "path"
- ]
- },
- "SpecificAssetId": {
- "allOf": [
- {
- "$ref": "#/definitions/HasSemantics"
- },
- {
- "properties": {
- "name": {
- "type": "string",
- "minLength": 1,
- "maxLength": 64,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- "value": {
- "type": "string",
- "minLength": 1,
- "maxLength": 2000,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- "externalSubjectId": {
- "$ref": "#/definitions/Reference"
- }
- },
- "required": [
- "name",
- "value"
- ]
- }
- ]
- },
- "StateOfEvent": {
- "type": "string",
- "enum": [
- "off",
- "on"
- ]
- },
- "Submodel": {
- "allOf": [
- {
- "$ref": "#/definitions/Identifiable"
- },
- {
- "$ref": "#/definitions/HasKind"
- },
- {
- "$ref": "#/definitions/HasSemantics"
- },
- {
- "$ref": "#/definitions/Qualifiable"
- },
- {
- "$ref": "#/definitions/HasDataSpecification"
- },
- {
- "properties": {
- "submodelElements": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/SubmodelElement_choice"
- },
- "minItems": 1
- },
- "modelType": {
- "const": "Submodel"
- }
- }
- }
- ]
- },
- "SubmodelElement": {
- "allOf": [
- {
- "$ref": "#/definitions/Referable"
- },
- {
- "$ref": "#/definitions/HasSemantics"
- },
- {
- "$ref": "#/definitions/Qualifiable"
- },
- {
- "$ref": "#/definitions/HasDataSpecification"
- }
- ]
- },
- "SubmodelElementCollection": {
- "allOf": [
- {
- "$ref": "#/definitions/SubmodelElement"
- },
- {
- "properties": {
- "value": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/SubmodelElement_choice"
- },
- "minItems": 1
- },
- "modelType": {
- "const": "SubmodelElementCollection"
- }
- }
- }
- ]
- },
- "SubmodelElementList": {
- "allOf": [
- {
- "$ref": "#/definitions/SubmodelElement"
- },
- {
- "properties": {
- "orderRelevant": {
- "type": "boolean"
- },
- "semanticIdListElement": {
- "$ref": "#/definitions/Reference"
- },
- "typeValueListElement": {
- "$ref": "#/definitions/AasSubmodelElements"
- },
- "valueTypeListElement": {
- "$ref": "#/definitions/DataTypeDefXsd"
- },
- "value": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/SubmodelElement_choice"
- },
- "minItems": 1
- },
- "modelType": {
- "const": "SubmodelElementList"
- }
- },
- "required": [
- "typeValueListElement"
- ]
- }
- ]
- },
- "SubmodelElement_choice": {
- "oneOf": [
- {
- "$ref": "#/definitions/RelationshipElement"
- },
- {
- "$ref": "#/definitions/AnnotatedRelationshipElement"
- },
- {
- "$ref": "#/definitions/BasicEventElement"
- },
- {
- "$ref": "#/definitions/Blob"
- },
- {
- "$ref": "#/definitions/Capability"
- },
- {
- "$ref": "#/definitions/Entity"
- },
- {
- "$ref": "#/definitions/File"
- },
- {
- "$ref": "#/definitions/MultiLanguageProperty"
- },
- {
- "$ref": "#/definitions/Operation"
- },
- {
- "$ref": "#/definitions/Property"
- },
- {
- "$ref": "#/definitions/Range"
- },
- {
- "$ref": "#/definitions/ReferenceElement"
- },
- {
- "$ref": "#/definitions/SubmodelElementCollection"
- },
- {
- "$ref": "#/definitions/SubmodelElementList"
- }
- ]
- },
- "ValueList": {
- "type": "object",
- "properties": {
- "valueReferencePairs": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/ValueReferencePair"
- },
- "minItems": 1
- }
- },
- "required": [
- "valueReferencePairs"
- ]
- },
- "ValueReferencePair": {
- "type": "object",
- "properties": {
- "value": {
- "type": "string",
- "minLength": 1,
- "maxLength": 2000,
- "pattern": "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$"
- },
- "valueId": {
- "$ref": "#/definitions/Reference"
- }
- },
- "required": [
- "value",
- "valueId"
- ]
- }
- }
-}
\ No newline at end of file
diff --git a/compliance_tool/aas_compliance_tool/schemas/aasXMLSchema.xsd b/compliance_tool/aas_compliance_tool/schemas/aasXMLSchema.xsd
deleted file mode 100644
index 95096ecb..00000000
--- a/compliance_tool/aas_compliance_tool/schemas/aasXMLSchema.xsd
+++ /dev/null
@@ -1,1344 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/compliance_tool/test/test_compliance_check_json.py b/compliance_tool/test/test_compliance_check_json.py
index a63d3909..656d1e50 100644
--- a/compliance_tool/test/test_compliance_check_json.py
+++ b/compliance_tool/test/test_compliance_check_json.py
@@ -12,50 +12,6 @@
class ComplianceToolJsonTest(unittest.TestCase):
- def test_check_schema(self) -> None:
- manager = ComplianceToolStateManager()
- script_dir = os.path.dirname(__file__)
- file_path_1 = os.path.join(script_dir, 'files/test_not_found.json')
- compliance_tool.check_schema(file_path_1, manager)
- self.assertEqual(3, len(manager.steps))
- self.assertEqual(Status.FAILED, manager.steps[0].status)
- self.assertEqual(Status.NOT_EXECUTED, manager.steps[1].status)
- self.assertEqual(Status.NOT_EXECUTED, manager.steps[2].status)
- self.assertIn("No such file or directory", manager.format_step(0, verbose_level=1))
-
- manager.steps = []
- file_path_2 = os.path.join(script_dir, 'files/test_not_deserializable.json')
- compliance_tool.check_schema(file_path_2, manager)
- self.assertEqual(3, len(manager.steps))
- self.assertEqual(Status.SUCCESS, manager.steps[0].status)
- self.assertEqual(Status.FAILED, manager.steps[1].status)
- self.assertEqual(Status.NOT_EXECUTED, manager.steps[2].status)
- self.assertIn("Expecting ',' delimiter: line 4 column 2 (char 54)", manager.format_step(1, verbose_level=1))
-
- manager.steps = []
- file_path_3 = os.path.join(script_dir, 'files/test_empty.json')
- compliance_tool.check_schema(file_path_3, manager)
- self.assertEqual(3, len(manager.steps))
- self.assertEqual(Status.SUCCESS, manager.steps[0].status)
- self.assertEqual(Status.SUCCESS, manager.steps[1].status)
- self.assertEqual(Status.SUCCESS, manager.steps[2].status)
-
- manager.steps = []
- file_path_4 = os.path.join(script_dir, 'files/test_demo_full_example.json')
- compliance_tool.check_schema(file_path_4, manager)
- self.assertEqual(3, len(manager.steps))
- self.assertEqual(Status.SUCCESS, manager.steps[0].status)
- self.assertEqual(Status.SUCCESS, manager.steps[1].status)
- self.assertEqual(Status.SUCCESS, manager.steps[2].status)
-
- manager.steps = []
- file_path_5 = os.path.join(script_dir, 'files/test_demo_full_example_wrong_attribute.json')
- compliance_tool.check_schema(file_path_5, manager)
- self.assertEqual(3, len(manager.steps))
- self.assertEqual(Status.SUCCESS, manager.steps[0].status)
- self.assertEqual(Status.SUCCESS, manager.steps[1].status)
- self.assertEqual(Status.SUCCESS, manager.steps[2].status)
-
def test_check_deserialization(self) -> None:
manager = ComplianceToolStateManager()
script_dir = os.path.dirname(__file__)
diff --git a/compliance_tool/test/test_compliance_check_xml.py b/compliance_tool/test/test_compliance_check_xml.py
index c7b023cc..7f5fbecc 100644
--- a/compliance_tool/test/test_compliance_check_xml.py
+++ b/compliance_tool/test/test_compliance_check_xml.py
@@ -12,41 +12,6 @@
class ComplianceToolXmlTest(unittest.TestCase):
- def test_check_schema(self) -> None:
- manager = ComplianceToolStateManager()
- script_dir = os.path.dirname(__file__)
- file_path_1 = os.path.join(script_dir, 'files/test_not_found.xml')
- compliance_tool.check_schema(file_path_1, manager)
- self.assertEqual(3, len(manager.steps))
- self.assertEqual(Status.FAILED, manager.steps[0].status)
- self.assertEqual(Status.NOT_EXECUTED, manager.steps[1].status)
- self.assertEqual(Status.NOT_EXECUTED, manager.steps[2].status)
- self.assertIn("No such file or directory", manager.format_step(0, verbose_level=1))
-
- manager.steps = []
- file_path_2 = os.path.join(script_dir, 'files/test_empty.xml')
- compliance_tool.check_schema(file_path_2, manager)
- self.assertEqual(3, len(manager.steps))
- self.assertEqual(Status.SUCCESS, manager.steps[0].status)
- self.assertEqual(Status.SUCCESS, manager.steps[1].status)
- self.assertEqual(Status.SUCCESS, manager.steps[2].status)
-
- manager.steps = []
- file_path_3 = os.path.join(script_dir, 'files/test_demo_full_example.xml')
- compliance_tool.check_schema(file_path_3, manager)
- self.assertEqual(3, len(manager.steps))
- self.assertEqual(Status.SUCCESS, manager.steps[0].status)
- self.assertEqual(Status.SUCCESS, manager.steps[1].status)
- self.assertEqual(Status.SUCCESS, manager.steps[2].status)
-
- manager.steps = []
- file_path_4 = os.path.join(script_dir, 'files/test_demo_full_example_wrong_attribute.xml')
- compliance_tool.check_schema(file_path_4, manager)
- self.assertEqual(3, len(manager.steps))
- self.assertEqual(Status.SUCCESS, manager.steps[0].status)
- self.assertEqual(Status.SUCCESS, manager.steps[1].status)
- self.assertEqual(Status.SUCCESS, manager.steps[2].status)
-
def test_check_deserialization(self) -> None:
manager = ComplianceToolStateManager()
script_dir = os.path.dirname(__file__)
From efea04693247690a40844298b1a1b36703aa8dfd Mon Sep 17 00:00:00 2001
From: Ornella33 <103257204+Ornella33@users.noreply.github.com>
Date: Tue, 19 May 2026 10:31:57 +0200
Subject: [PATCH 09/12] Revert `ServerAASFromJsonDecoder` direct import (#550)
Previously, `provider.py` imported `ServerAASFromJsonDecoder` directly
from `app.adapter`, which caused a circular import.
Reverted the import to use the `app.adapter` module reference instead,
accessing `adapter.ServerAASFromJsonDecoder` at the call site.
Co-authored-by: s-heppner
---
server/app/model/provider.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/server/app/model/provider.py b/server/app/model/provider.py
index 409570fe..472f0997 100644
--- a/server/app/model/provider.py
+++ b/server/app/model/provider.py
@@ -5,7 +5,7 @@
from basyx.aas import model
from basyx.aas.model import provider as sdk_provider
-from app.adapter import ServerAASFromJsonDecoder
+from app import adapter
from app.model import descriptor
PathOrIO = Union[Path, IO]
@@ -66,7 +66,7 @@ def load_directory(directory: Union[Path, str]) -> DictDescriptorStore:
if not file.is_file() or file.suffix.lower() != ".json":
continue
with open(file) as f:
- data = json.load(f, cls=ServerAASFromJsonDecoder)
+ data = json.load(f, cls=adapter.ServerAASFromJsonDecoder)
for item in data.get("assetAdministrationShellDescriptors", []):
if isinstance(item, descriptor.AssetAdministrationShellDescriptor):
try:
From b4be7bca5a32d4afbb524d1948bb8374104f8b5d Mon Sep 17 00:00:00 2001
From: Tamas Farkas
Date: Fri, 22 May 2026 10:07:31 +0200
Subject: [PATCH 10/12] Fix PUT not persisting changes in
LocalFileIdentifiableStore
update_from() only mutated the in-memory object; the next GET re-read
the unchanged file from disk and overwrote the in-memory state, silently
discarding the update.
Add LocalFileIdentifiableStore.commit() to write an identifiable back to
its storage file, and call it from put_submodel and
put_submodel_submodel_elements_id_short_path after update_from().
---
sdk/basyx/aas/backend/local_file.py | 16 ++++++++++++++++
server/app/interfaces/repository.py | 5 +++++
2 files changed, 21 insertions(+)
diff --git a/sdk/basyx/aas/backend/local_file.py b/sdk/basyx/aas/backend/local_file.py
index 72d5605a..1c5e5971 100644
--- a/sdk/basyx/aas/backend/local_file.py
+++ b/sdk/basyx/aas/backend/local_file.py
@@ -110,6 +110,22 @@ def add(self, x: model.Identifiable) -> None:
with self._object_cache_lock:
self._object_cache[x.id] = x
+ def commit(self, x: model.Identifiable) -> None:
+ """
+ Write an updated :class:`~basyx.aas.model.base.Identifiable` object back to its storage file.
+
+ Use this after mutating an object that was retrieved from the store to persist the changes to disk.
+
+ :param x: The object to persist
+ :raises KeyError: If the object does not exist in the database
+ """
+ logger.debug("Committing object %s to Local File Store ...", repr(x))
+ path = "{}/{}.json".format(self.directory_path, self._transform_id(x.id))
+ if not os.path.exists(path):
+ raise KeyError("No AAS object with id {} exists in local file database".format(x.id))
+ with open(path, "w") as file:
+ json.dump({"data": x}, file, cls=json_serialization.AASToJsonEncoder, indent=4)
+
def discard(self, x: model.Identifiable) -> None:
"""
Delete an :class:`~basyx.aas.model.base.Identifiable` AAS object from the local file store
diff --git a/server/app/interfaces/repository.py b/server/app/interfaces/repository.py
index 89ad0d64..faff38cc 100644
--- a/server/app/interfaces/repository.py
+++ b/server/app/interfaces/repository.py
@@ -708,6 +708,8 @@ def get_submodels_reference(
def put_submodel(self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs) -> Response:
submodel = self._get_submodel(url_args)
submodel.update_from(HTTPApiDecoder.request_body(request, model.Submodel, is_stripped_request(request)))
+ if hasattr(self.object_store, 'commit'):
+ self.object_store.commit(submodel)
return response_t()
def get_submodel_submodel_elements(
@@ -787,6 +789,7 @@ def post_submodel_submodel_elements_id_short_path(
def put_submodel_submodel_elements_id_short_path(
self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs
) -> Response:
+ submodel = self._get_submodel(url_args)
submodel_element = self._get_submodel_submodel_elements_id_short_path(url_args)
# TODO: remove the following type: ignore comment when mypy supports abstract types for Type[T]
# see https://github.com/python/mypy/issues/5374
@@ -794,6 +797,8 @@ def put_submodel_submodel_elements_id_short_path(
request, model.SubmodelElement, is_stripped_request(request) # type: ignore[type-abstract]
)
submodel_element.update_from(new_submodel_element)
+ if hasattr(self.object_store, 'commit'):
+ self.object_store.commit(submodel)
return response_t()
def delete_submodel_submodel_elements_id_short_path(
From 0005995caebcb0a40de5c97207384bdea047ea76 Mon Sep 17 00:00:00 2001
From: Tamas Farkas
Date: Fri, 22 May 2026 12:07:55 +0200
Subject: [PATCH 11/12] Implement PATCH endpoints for submodels and submodel
elements
Six routes were previously returning 501 Not Implemented:
- PATCH /submodels/{smId}
- PATCH /submodels/{smId}/$metadata
- PATCH /submodels/{smId}/$value
- PATCH /submodels/{smId}/submodel-elements/{path}
- PATCH /submodels/{smId}/submodel-elements/{path}/$metadata
- PATCH /submodels/{smId}/submodel-elements/{path}/$value
The plain and $metadata variants decode the full/stripped body and
call update_from. The $value variants use a new _apply_value_only
helper that applies a ValueOnly patch to the in-memory element tree.
All handlers call commit() to persist changes to disk.
---
server/app/interfaces/repository.py | 126 ++++++++++++++++++++++++++--
1 file changed, 117 insertions(+), 9 deletions(-)
diff --git a/server/app/interfaces/repository.py b/server/app/interfaces/repository.py
index faff38cc..2245140a 100644
--- a/server/app/interfaces/repository.py
+++ b/server/app/interfaces/repository.py
@@ -8,9 +8,10 @@
This module implements the "Specification of the Asset Administration Shell Part 2 Application Programming Interfaces".
"""
+import base64
import io
import json
-from typing import Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Type, Union
+from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Type, Union
import werkzeug.exceptions
import werkzeug.routing
@@ -124,14 +125,14 @@ def __init__(
Rule("/", methods=["GET"], endpoint=self.get_submodel),
Rule("/", methods=["PUT"], endpoint=self.put_submodel),
Rule("/", methods=["DELETE"], endpoint=self.delete_submodel),
- Rule("/", methods=["PATCH"], endpoint=self.not_implemented),
+ Rule("/", methods=["PATCH"], endpoint=self.patch_submodel),
Submount(
"/",
[
Rule("/$metadata", methods=["GET"], endpoint=self.get_submodels_metadata),
- Rule("/$metadata", methods=["PATCH"], endpoint=self.not_implemented),
+ Rule("/$metadata", methods=["PATCH"], endpoint=self.patch_submodel_metadata),
Rule("/$value", methods=["GET"], endpoint=self.not_implemented),
- Rule("/$value", methods=["PATCH"], endpoint=self.not_implemented),
+ Rule("/$value", methods=["PATCH"], endpoint=self.patch_submodel_value),
Rule("/$reference", methods=["GET"], endpoint=self.get_submodels_reference),
Rule("/$path", methods=["GET"], endpoint=self.not_implemented),
Rule(
@@ -182,7 +183,7 @@ def __init__(
Rule(
"/",
methods=["PATCH"],
- endpoint=self.not_implemented,
+ endpoint=self.patch_submodel_submodel_elements_id_short_path,
),
Submount(
"/",
@@ -195,7 +196,7 @@ def __init__(
Rule(
"/$metadata",
methods=["PATCH"],
- endpoint=self.not_implemented,
+ endpoint=self.patch_submodel_submodel_elements_id_short_path_metadata, # noqa: E501
),
Rule(
"/$reference",
@@ -204,7 +205,8 @@ def __init__(
),
Rule("/$value", methods=["GET"], endpoint=self.not_implemented),
Rule(
- "/$value", methods=["PATCH"], endpoint=self.not_implemented
+ "/$value", methods=["PATCH"],
+ endpoint=self.patch_submodel_submodel_elements_id_short_path_value, # noqa: E501
),
Rule("/$path", methods=["GET"], endpoint=self.not_implemented),
Rule(
@@ -708,7 +710,7 @@ def get_submodels_reference(
def put_submodel(self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs) -> Response:
submodel = self._get_submodel(url_args)
submodel.update_from(HTTPApiDecoder.request_body(request, model.Submodel, is_stripped_request(request)))
- if hasattr(self.object_store, 'commit'):
+ if hasattr(self.object_store, "commit"):
self.object_store.commit(submodel)
return response_t()
@@ -797,10 +799,116 @@ def put_submodel_submodel_elements_id_short_path(
request, model.SubmodelElement, is_stripped_request(request) # type: ignore[type-abstract]
)
submodel_element.update_from(new_submodel_element)
- if hasattr(self.object_store, 'commit'):
+ if hasattr(self.object_store, "commit"):
+ self.object_store.commit(submodel)
+ return response_t()
+
+ def patch_submodel(self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs) -> Response:
+ submodel = self._get_submodel(url_args)
+ submodel.update_from(HTTPApiDecoder.request_body(request, model.Submodel, is_stripped_request(request)))
+ if hasattr(self.object_store, "commit"):
+ self.object_store.commit(submodel)
+ return response_t()
+
+ def patch_submodel_metadata(
+ self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs
+ ) -> Response:
+ submodel = self._get_submodel(url_args)
+ submodel.update_from(HTTPApiDecoder.request_body(request, model.Submodel, True))
+ if hasattr(self.object_store, "commit"):
+ self.object_store.commit(submodel)
+ return response_t()
+
+ def patch_submodel_value(
+ self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs
+ ) -> Response:
+ submodel = self._get_submodel(url_args)
+ value_data = json.loads(request.get_data())
+ if not isinstance(value_data, dict):
+ raise BadRequest("ValueOnly body for a submodel must be a JSON object")
+ for id_short, child_value in value_data.items():
+ child = next((e for e in submodel.submodel_element if e.id_short == id_short), None)
+ if child is None:
+ raise BadRequest(f"No submodel element with idShort {id_short!r}")
+ self._apply_value_only(child, child_value)
+ if hasattr(self.object_store, "commit"):
+ self.object_store.commit(submodel)
+ return response_t()
+
+ def patch_submodel_submodel_elements_id_short_path(
+ self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs
+ ) -> Response:
+ submodel = self._get_submodel(url_args)
+ submodel_element = self._get_submodel_submodel_elements_id_short_path(url_args)
+ new_submodel_element = HTTPApiDecoder.request_body(
+ request, model.SubmodelElement, is_stripped_request(request) # type: ignore[type-abstract]
+ )
+ submodel_element.update_from(new_submodel_element)
+ if hasattr(self.object_store, "commit"):
self.object_store.commit(submodel)
return response_t()
+ def patch_submodel_submodel_elements_id_short_path_metadata(
+ self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs
+ ) -> Response:
+ submodel = self._get_submodel(url_args)
+ submodel_element = self._get_submodel_submodel_elements_id_short_path(url_args)
+ new_submodel_element = HTTPApiDecoder.request_body(
+ request, model.SubmodelElement, True # type: ignore[type-abstract]
+ )
+ submodel_element.update_from(new_submodel_element)
+ if hasattr(self.object_store, "commit"):
+ self.object_store.commit(submodel)
+ return response_t()
+
+ def patch_submodel_submodel_elements_id_short_path_value(
+ self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs
+ ) -> Response:
+ submodel = self._get_submodel(url_args)
+ submodel_element = self._get_submodel_submodel_elements_id_short_path(url_args)
+ value_data = json.loads(request.get_data())
+ self._apply_value_only(submodel_element, value_data)
+ if hasattr(self.object_store, "commit"):
+ self.object_store.commit(submodel)
+ return response_t()
+
+ def _apply_value_only(self, element: model.SubmodelElement, value_data: Any) -> None:
+ if isinstance(element, model.Property):
+ element.value = (
+ model.datatypes.from_xsd(str(value_data), element.value_type)
+ if value_data is not None else None
+ )
+ elif isinstance(element, model.MultiLanguageProperty):
+ element.value = (
+ model.MultiLanguageTextType({item["language"]: item["text"] for item in value_data})
+ if value_data is not None else None
+ )
+ elif isinstance(element, model.Range):
+ element.min = model.datatypes.from_xsd(str(value_data["min"]), element.value_type)
+ element.max = model.datatypes.from_xsd(str(value_data["max"]), element.value_type)
+ elif isinstance(element, model.Blob):
+ element.value = base64.b64decode(value_data) if value_data is not None else None
+ elif isinstance(element, model.File):
+ element.value = value_data
+ elif isinstance(element, model.SubmodelElementCollection):
+ if not isinstance(value_data, dict):
+ raise BadRequest("ValueOnly for SubmodelElementCollection must be a JSON object")
+ for id_short, child_value in value_data.items():
+ child = next((e for e in element.submodel_element if e.id_short == id_short), None)
+ if child is None:
+ raise BadRequest(f"No submodel element with idShort {id_short!r} in {element.id_short!r}")
+ self._apply_value_only(child, child_value)
+ elif isinstance(element, model.SubmodelElementList):
+ if not isinstance(value_data, list):
+ raise BadRequest("ValueOnly for SubmodelElementList must be a JSON array")
+ elements = list(element.value)
+ for i, child_value in enumerate(value_data):
+ if i >= len(elements):
+ raise BadRequest(f"Index {i} out of range for SubmodelElementList {element.id_short!r}")
+ self._apply_value_only(elements[i], child_value)
+ else:
+ raise BadRequest(f"ValueOnly PATCH not supported for {type(element).__name__}")
+
def delete_submodel_submodel_elements_id_short_path(
self, request: Request, url_args: Dict, response_t: Type[APIResponse], **_kwargs
) -> Response:
From bd9b8256bcc358a375ed3f93b73ec72ec24b6891 Mon Sep 17 00:00:00 2001
From: Tamas Farkas
Date: Fri, 22 May 2026 12:24:48 +0200
Subject: [PATCH 12/12] Fix SubmodelElementCollection attribute name in
_apply_value_only
---
server/app/interfaces/repository.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/server/app/interfaces/repository.py b/server/app/interfaces/repository.py
index 2245140a..c7db5a33 100644
--- a/server/app/interfaces/repository.py
+++ b/server/app/interfaces/repository.py
@@ -894,7 +894,7 @@ def _apply_value_only(self, element: model.SubmodelElement, value_data: Any) ->
if not isinstance(value_data, dict):
raise BadRequest("ValueOnly for SubmodelElementCollection must be a JSON object")
for id_short, child_value in value_data.items():
- child = next((e for e in element.submodel_element if e.id_short == id_short), None)
+ child = next((e for e in element.value if e.id_short == id_short), None)
if child is None:
raise BadRequest(f"No submodel element with idShort {id_short!r} in {element.id_short!r}")
self._apply_value_only(child, child_value)