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__)
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/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/basyx/aas/backend/local_file.py b/sdk/basyx/aas/backend/local_file.py
index 4008497a..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
@@ -150,7 +166,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 +177,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/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/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):
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):
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()
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))
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/interfaces/repository.py b/server/app/interfaces/repository.py
index 0e75eedd..c7db5a33 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(
@@ -470,10 +472,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,
)
@@ -711,6 +710,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(
@@ -790,6 +791,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
@@ -797,8 +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"):
+ 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.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)
+ 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:
diff --git a/server/app/model/provider.py b/server/app/model/provider.py
index 97067e7d..472f0997 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 import adapter
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=adapter.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
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"
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)