MTPPy2.0 0.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- examples/__init__.py +0 -0
- examples/minimal+pid.py +82 -0
- examples/minimal.py +32 -0
- examples/minimal_recipe.py +117 -0
- examples/pea_alarm_base.py +47 -0
- examples/pea_minimal.py +258 -0
- mtppy/__init__.py +1 -0
- mtppy/part1/__init__.py +4 -0
- mtppy/part1/empty_folder_test.py +7 -0
- mtppy/part3/__init__.py +4 -0
- mtppy/part3/data_assembly_set/__init__.py +4 -0
- mtppy/part3/data_assembly_set/active_elements/__init__.py +4 -0
- mtppy/part3/data_assembly_set/active_elements/active_element.py +22 -0
- mtppy/part3/data_assembly_set/active_elements/drives/__init__.py +4 -0
- mtppy/part3/data_assembly_set/active_elements/drives/ana_drv.py +446 -0
- mtppy/part3/data_assembly_set/active_elements/drives/ana_drv_test.py +567 -0
- mtppy/part3/data_assembly_set/active_elements/drives/bin_drv.py +365 -0
- mtppy/part3/data_assembly_set/active_elements/drives/bin_drv_test.py +503 -0
- mtppy/part3/data_assembly_set/active_elements/drives/mon_ana_drv.py +469 -0
- mtppy/part3/data_assembly_set/active_elements/drives/mon_ana_drv_test.py +418 -0
- mtppy/part3/data_assembly_set/active_elements/drives/mon_bin_drv.py +295 -0
- mtppy/part3/data_assembly_set/active_elements/drives/mon_bin_drv_test.py +265 -0
- mtppy/part3/data_assembly_set/active_elements/pid/__init__.py +4 -0
- mtppy/part3/data_assembly_set/active_elements/pid/pid_ctrl.py +333 -0
- mtppy/part3/data_assembly_set/active_elements/pid/pid_ctrl_test.py +156 -0
- mtppy/part3/data_assembly_set/active_elements/valves/__init__.py +4 -0
- mtppy/part3/data_assembly_set/active_elements/valves/ana_vlv.py +402 -0
- mtppy/part3/data_assembly_set/active_elements/valves/ana_vlv_test.py +732 -0
- mtppy/part3/data_assembly_set/active_elements/valves/bin_vlv.py +279 -0
- mtppy/part3/data_assembly_set/active_elements/valves/bin_vlv_test.py +585 -0
- mtppy/part3/data_assembly_set/active_elements/valves/mon_ana_vlv.py +402 -0
- mtppy/part3/data_assembly_set/active_elements/valves/mon_ana_vlv_test.py +270 -0
- mtppy/part3/data_assembly_set/active_elements/valves/mon_bin_vlv.py +286 -0
- mtppy/part3/data_assembly_set/active_elements/valves/mon_bin_vlv_test.py +224 -0
- mtppy/part3/data_assembly_set/base_functions/__init__.py +4 -0
- mtppy/part3/data_assembly_set/base_functions/operation_mode.py +252 -0
- mtppy/part3/data_assembly_set/base_functions/operation_mode_test.py +260 -0
- mtppy/part3/data_assembly_set/base_functions/oslevel.py +35 -0
- mtppy/part3/data_assembly_set/base_functions/source_mode.py +189 -0
- mtppy/part3/data_assembly_set/base_functions/source_mode_test.py +112 -0
- mtppy/part3/data_assembly_set/base_functions/wqc.py +75 -0
- mtppy/part3/data_assembly_set/base_functions/wqc_test.py +86 -0
- mtppy/part3/data_assembly_set/data_assembly.py +65 -0
- mtppy/part3/data_assembly_set/diagnostic_elements/__init__.py +4 -0
- mtppy/part3/data_assembly_set/diagnostic_elements/diagnostic_element.py +19 -0
- mtppy/part3/data_assembly_set/indicator_elements/__init__.py +4 -0
- mtppy/part3/data_assembly_set/indicator_elements/indicator_element.py +22 -0
- mtppy/part3/data_assembly_set/indicator_elements/views/__init__.py +4 -0
- mtppy/part3/data_assembly_set/indicator_elements/views/ana_view.py +68 -0
- mtppy/part3/data_assembly_set/indicator_elements/views/bin_view.py +73 -0
- mtppy/part3/data_assembly_set/indicator_elements/views/dint_view.py +76 -0
- mtppy/part3/data_assembly_set/indicator_elements/views/string_view.py +55 -0
- mtppy/part3/data_assembly_set/operation_elements/__init__.py +4 -0
- mtppy/part3/data_assembly_set/operation_elements/operation_element.py +19 -0
- mtppy/part3/data_assembly_set/pea_elements/__init__.py +4 -0
- mtppy/part3/data_assembly_set/pea_elements/pea_element.py +28 -0
- mtppy/part3/data_assembly_set/pea_elements/pea_information_label/__init__.py +4 -0
- mtppy/part3/data_assembly_set/pea_elements/pea_information_label/pea_information_label.py +231 -0
- mtppy/part3/data_assembly_set/pea_elements/web_server_url_info/__init__.py +4 -0
- mtppy/part3/data_assembly_set/pea_elements/web_server_url_info/web_server_url_info.py +42 -0
- mtppy/part3/definitions/__init__.py +4 -0
- mtppy/part3/definitions/mtp_access.py +15 -0
- mtppy/part4/__init__.py +4 -0
- mtppy/part4/automation_services/__init__.py +4 -0
- mtppy/part4/automation_services/base_functions/__init__.py +4 -0
- mtppy/part4/automation_services/base_functions/service_operation_mode.py +368 -0
- mtppy/part4/automation_services/base_functions/service_operation_mode_test.py +242 -0
- mtppy/part4/automation_services/base_functions/service_operator_interaction.py +62 -0
- mtppy/part4/automation_services/base_functions/service_source_mode.py +138 -0
- mtppy/part4/automation_services/base_functions/service_source_mode_test.py +179 -0
- mtppy/part4/automation_services/helpers/__init__.py +4 -0
- mtppy/part4/automation_services/helpers/command_en_control.py +330 -0
- mtppy/part4/automation_services/helpers/command_en_control_test.py +302 -0
- mtppy/part4/automation_services/helpers/state_machine.py +167 -0
- mtppy/part4/automation_services/helpers/state_machine_test.py +294 -0
- mtppy/part4/automation_services/helpers/thread_control.py +39 -0
- mtppy/part4/automation_services/helpers/thread_control_test.py +80 -0
- mtppy/part4/automation_services/procedure.py +111 -0
- mtppy/part4/automation_services/procedure_test.py +185 -0
- mtppy/part4/automation_services/service.py +463 -0
- mtppy/part4/automation_services/service_elements/__init__.py +4 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/__init__.py +4 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/ana_serv_param.py +108 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/ana_serv_param_test.py +59 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/bin_serv_param.py +70 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/bin_serv_param_test.py +39 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/common_serv_param.py +111 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/dint_serv_param.py +77 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/dint_serv_param_test.py +58 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/parameter_element.py +175 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/parameter_element_test.py +246 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/string_serv_param.py +48 -0
- mtppy/part4/automation_services/service_elements/parameter_elements/string_serv_param_test.py +37 -0
- mtppy/part4/automation_services/service_elements/procedure_health_view.py +36 -0
- mtppy/part4/automation_services/service_elements/service_control.py +473 -0
- mtppy/part4/automation_services/service_elements/service_control_test.py +150 -0
- mtppy/part4/automation_services/service_elements/service_element.py +20 -0
- mtppy/part4/automation_services/service_test.py +407 -0
- mtppy/part4/definitions/__init__.py +4 -0
- mtppy/part4/definitions/command_codes.py +58 -0
- mtppy/part4/definitions/command_codes_test.py +44 -0
- mtppy/part4/definitions/state_codes.py +65 -0
- mtppy/part4/definitions/state_codes_test.py +44 -0
- mtppy/part4/process_values/__init__.py +4 -0
- mtppy/part4/process_values/base_functions/__init__.py +4 -0
- mtppy/part4/process_values/base_functions/vqc.py +48 -0
- mtppy/part4/process_values/input_elements/__init__.py +4 -0
- mtppy/part4/process_values/input_elements/ana_process_value_in.py +160 -0
- mtppy/part4/process_values/input_elements/bin_process_value_in.py +83 -0
- mtppy/part4/process_values/input_elements/dint_process_value_in.py +159 -0
- mtppy/part4/process_values/input_elements/input_element.py +40 -0
- mtppy/part4/process_values/input_elements/string_process_value_in.py +45 -0
- mtppy/part5/__init__.py +4 -0
- mtppy/part5/communication_object.py +99 -0
- mtppy/part5/communication_object_test.py +59 -0
- mtppy/part5/definitions/__init__.py +4 -0
- mtppy/part5/definitions/iec61131_data_types.py +31 -0
- mtppy/part5/definitions/iec61131_data_types_test.py +82 -0
- mtppy/part5/definitions/opcua_data_types.py +36 -0
- mtppy/part5/definitions/opcua_data_types_test.py +90 -0
- mtppy/part5/definitions/w3c_xml_data_types.py +177 -0
- mtppy/part5/definitions/w3c_xml_data_types_test.py +143 -0
- mtppy/part5/server_assembly_set/__init__.py +5 -0
- mtppy/part6/__init__.py +4 -0
- mtppy/part6/alarm_set/__init__.py +4 -0
- mtppy/part6/alarm_set/alarm.py +132 -0
- mtppy/part6/alarm_set/alarm_test.py +265 -0
- mtppy/pea/__init__.py +19 -0
- mtppy/pea/data_assembly_factory.py +124 -0
- mtppy/pea/data_assembly_factory_test.py +229 -0
- mtppy/pea/mtp_generator/__init__.py +4 -0
- mtppy/pea/mtp_generator/mtp_generator.py +1183 -0
- mtppy/pea/mtp_generator/mtp_generator_test.py +518 -0
- mtppy/pea/pea.py +139 -0
- mtppy/pea/pea_opcua_server/__init__.py +4 -0
- mtppy/pea/pea_opcua_server/marshalling.py +60 -0
- mtppy/pea/pea_opcua_server/opcua_options_provider/__init__.py +4 -0
- mtppy/pea/pea_opcua_server/opcua_options_provider/instances/basic_opcua_options_provider.py +50 -0
- mtppy/pea/pea_opcua_server/opcua_options_provider/opcua_options_provider.py +103 -0
- mtppy/pea/pea_opcua_server/pea_opcua_server.py +492 -0
- mtppy/pea/pea_opcua_server/subscription_list.py +55 -0
- mtppy/pea/pea_test.py +59 -0
- mtppy/pea/rmtp_pea.py +155 -0
- mtppy/pea/rmtp_pea_test.py +27 -0
- mtppy/pea/service_factory.py +178 -0
- mtppy/pea/service_factory_test.py +156 -0
- mtppy/pea/user_logic/__init__.py +4 -0
- mtppy/pea/user_logic/user_logic_pea.py +72 -0
- mtppy/pea/user_logic/user_logic_registry.py +106 -0
- mtppy/pea/user_logic/user_logic_registry_test.py +167 -0
- mtppy/pea/user_logic/user_logic_service.py +228 -0
- mtppy/pea/user_logic/user_logic_service_test.py +289 -0
- mtppy/utils/__init__.py +4 -0
- mtppy/utils/data_assembly_dict.py +61 -0
- mtppy/utils/data_assembly_dict_test.py +107 -0
- mtppy/utils/data_item.py +446 -0
- mtppy/utils/data_item_com_test.py +82 -0
- mtppy/utils/data_item_test.py +325 -0
- mtppy/utils/logging.py +23 -0
- mtppy/utils/logging_test.py +26 -0
- mtppy/utils/mtp_file_handling/__init__.py +4 -0
- mtppy/utils/mtp_file_handling/extract_manifest_from_mtp_file.py +22 -0
- mtppy/utils/mtp_file_handling/extract_mtp_file.py +22 -0
- mtppy/utils/mtp_file_handling/is_valid_mtp.py +22 -0
- mtppy/utils/mtp_file_handling/is_valid_path_for_manifest.py +15 -0
- mtppy/utils/mtp_file_handling/is_valid_path_to_manifest.py +17 -0
- mtppy/utils/mtp_file_handling/is_valid_path_to_mtp.py +17 -0
- mtppy/utils/mtp_file_handling/load_manifest_file.py +24 -0
- mtppy/utils/mtp_file_handling/mtp_reader/__init__.py +4 -0
- mtppy/utils/mtp_file_handling/mtp_reader/mtp_reader.py +103 -0
- mtppy/utils/mtp_file_handling/mtp_reader/mtp_reader_test.py +96 -0
- mtppy/utils/mtp_file_handling/mtp_reader/mtp_xml.py +226 -0
- mtppy/utils/mtp_file_handling/mtp_reader/mtp_xml_test.py +108 -0
- mtppy/utils/mtp_file_handling/mtp_writer/__init__.py +4 -0
- mtppy/utils/mtp_file_handling/mtp_writer/mtp_writer.py +77 -0
- mtppy/utils/mtp_file_handling/mtp_writer/ressources/get_content_types.py +10 -0
- mtppy/utils/mtp_file_handling/mtp_writer/ressources/get_rels.py +11 -0
- mtppy/utils/mtp_file_handling/mtp_writer/test_mtp_writer.py +25 -0
- mtppy/utils/mtp_file_handling/write_manifest_file.py +29 -0
- mtppy/utils/mtp_file_handling/write_manifest_file_test.py +22 -0
- mtppy/utils/mtp_file_handling/write_mtp_file.py +43 -0
- mtppy/utils/mtp_file_handling/write_mtp_file_test.py +22 -0
- mtppy/utils/testing.py +31 -0
- mtppy2_0-0.1.5.dist-info/METADATA +75 -0
- mtppy2_0-0.1.5.dist-info/RECORD +214 -0
- mtppy2_0-0.1.5.dist-info/WHEEL +5 -0
- mtppy2_0-0.1.5.dist-info/entry_points.txt +3 -0
- mtppy2_0-0.1.5.dist-info/licenses/LICENSE +21 -0
- mtppy2_0-0.1.5.dist-info/top_level.txt +4 -0
- tools/__init__.py +0 -0
- tools/create_bugreport.py +150 -0
- tools/inspect_mtp.py +110 -0
- utils/__init__.py +1 -0
- utils/file_handling/__init__.py +4 -0
- utils/file_handling/cleanup_folder.py +9 -0
- utils/file_handling/get_utc_foldername_with_ms.py +15 -0
- utils/file_handling/get_work_dir.py +9 -0
- utils/file_handling/is_valid_path_for_json.py +11 -0
- utils/file_handling/is_valid_path_to_json.py +17 -0
- utils/file_handling/load_json_file.py +18 -0
- utils/file_handling/read_file_from_file_archive.py +43 -0
- utils/file_handling/read_file_string.py +10 -0
- utils/file_handling/validate_json_data.py +28 -0
- utils/file_handling/write_json_file.py +23 -0
- utils/get_uuid.py +18 -0
- utils/get_uuid_test.py +17 -0
- utils/logging.py +100 -0
- utils/logging_test.py +67 -0
- utils/opcua/check_node.py +79 -0
- utils/resolve_unit/resolve_unit.py +11 -0
- utils/resolve_unit/unit_lib.py +2101 -0
- utils/resolve_unit/unit_lib_test.py +36 -0
- utils/type_conversion.py +236 -0
- utils/type_conversion_test.py +114 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Tests for IEC-61131 data types."""
|
|
2
|
+
|
|
3
|
+
__copyright__ = (
|
|
4
|
+
"Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
|
|
5
|
+
)
|
|
6
|
+
__license__ = "MIT"
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from enum import Enum
|
|
11
|
+
|
|
12
|
+
from mtppy.part5.definitions.iec61131_data_types import IEC61131DataTypes
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_iec61131_data_types_is_enum() -> None:
|
|
16
|
+
assert issubclass(IEC61131DataTypes, Enum)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_iec61131_data_types_contains_all_supported_types() -> None:
|
|
20
|
+
expected_names = [
|
|
21
|
+
"BOOL",
|
|
22
|
+
"BYTE",
|
|
23
|
+
"WORD",
|
|
24
|
+
"DWORD",
|
|
25
|
+
"LWORD",
|
|
26
|
+
"SINT",
|
|
27
|
+
"INT",
|
|
28
|
+
"DINT",
|
|
29
|
+
"LINT",
|
|
30
|
+
"USINT",
|
|
31
|
+
"UINT",
|
|
32
|
+
"UDINT",
|
|
33
|
+
"ULINT",
|
|
34
|
+
"REAL",
|
|
35
|
+
"LREAL",
|
|
36
|
+
"STRING",
|
|
37
|
+
"WSTRING",
|
|
38
|
+
"DATE",
|
|
39
|
+
"TIME_OF_DAY",
|
|
40
|
+
"DATE_AND_TIME",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
assert [member.name for member in IEC61131DataTypes] == expected_names
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.mark.parametrize(
|
|
47
|
+
("member", "expected_value"),
|
|
48
|
+
[
|
|
49
|
+
(member_name, (member_name,))
|
|
50
|
+
for member_name in (
|
|
51
|
+
"BOOL",
|
|
52
|
+
"BYTE",
|
|
53
|
+
"WORD",
|
|
54
|
+
"DWORD",
|
|
55
|
+
"LWORD",
|
|
56
|
+
"SINT",
|
|
57
|
+
"INT",
|
|
58
|
+
"DINT",
|
|
59
|
+
"LINT",
|
|
60
|
+
"USINT",
|
|
61
|
+
"UINT",
|
|
62
|
+
"UDINT",
|
|
63
|
+
"ULINT",
|
|
64
|
+
"REAL",
|
|
65
|
+
"LREAL",
|
|
66
|
+
"STRING",
|
|
67
|
+
"WSTRING",
|
|
68
|
+
"DATE",
|
|
69
|
+
"TIME_OF_DAY",
|
|
70
|
+
)
|
|
71
|
+
]
|
|
72
|
+
+ [("DATE_AND_TIME", "DATE_AND_TIME")],
|
|
73
|
+
)
|
|
74
|
+
def test_iec61131_data_type_values(
|
|
75
|
+
member: str, expected_value: tuple[str] | str
|
|
76
|
+
) -> None:
|
|
77
|
+
assert IEC61131DataTypes[member].value == expected_value
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_iec61131_data_types_can_be_looked_up_by_value() -> None:
|
|
81
|
+
assert IEC61131DataTypes(("BOOL",)) is IEC61131DataTypes.BOOL
|
|
82
|
+
assert IEC61131DataTypes("DATE_AND_TIME") is IEC61131DataTypes.DATE_AND_TIME
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Definition of String Constants for OPCUA Data Types."""
|
|
2
|
+
|
|
3
|
+
__copyright__ = (
|
|
4
|
+
"Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
|
|
5
|
+
)
|
|
6
|
+
__license__ = "MIT"
|
|
7
|
+
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OpcUaDataTypes(Enum):
|
|
12
|
+
Boolean = ("Boolean",)
|
|
13
|
+
SByte = ("SByte",)
|
|
14
|
+
Byte = ("Byte",)
|
|
15
|
+
Int16 = ("Int16",)
|
|
16
|
+
UInt16 = ("UInt16",)
|
|
17
|
+
Int32 = ("Int32",)
|
|
18
|
+
UInt32 = ("UInt32",)
|
|
19
|
+
Int64 = ("Int64",)
|
|
20
|
+
UInt64 = ("UInt64",)
|
|
21
|
+
Float = ("Float",)
|
|
22
|
+
Double = ("Double",)
|
|
23
|
+
String = ("String",)
|
|
24
|
+
DateTime = ("DateTime",)
|
|
25
|
+
Guid = ("Guid",)
|
|
26
|
+
ByteString = ("ByteString",)
|
|
27
|
+
XmlElement = ("XmlElement",)
|
|
28
|
+
NodeId = ("NodeId",)
|
|
29
|
+
ExpandedNodeId = ("ExpandedNodeId",)
|
|
30
|
+
StatusCode = ("StatusCode",)
|
|
31
|
+
QualifiedName = ("QualifiedName",)
|
|
32
|
+
LocalizedText = ("LocalizedText",)
|
|
33
|
+
ExtensionObject = ("ExtensionObject",)
|
|
34
|
+
DataValue = ("DataValue",)
|
|
35
|
+
Variant = ("Variant",)
|
|
36
|
+
DiagnosticInfo = "DiagnosticInfo"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Tests for OPC UA data types."""
|
|
2
|
+
|
|
3
|
+
__copyright__ = (
|
|
4
|
+
"Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
|
|
5
|
+
)
|
|
6
|
+
__license__ = "MIT"
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from enum import Enum
|
|
11
|
+
|
|
12
|
+
from mtppy.part5.definitions.opcua_data_types import OpcUaDataTypes
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_opcua_data_types_is_enum() -> None:
|
|
16
|
+
assert issubclass(OpcUaDataTypes, Enum)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_opcua_data_types_contains_all_supported_types() -> None:
|
|
20
|
+
expected_names = [
|
|
21
|
+
"Boolean",
|
|
22
|
+
"SByte",
|
|
23
|
+
"Byte",
|
|
24
|
+
"Int16",
|
|
25
|
+
"UInt16",
|
|
26
|
+
"Int32",
|
|
27
|
+
"UInt32",
|
|
28
|
+
"Int64",
|
|
29
|
+
"UInt64",
|
|
30
|
+
"Float",
|
|
31
|
+
"Double",
|
|
32
|
+
"String",
|
|
33
|
+
"DateTime",
|
|
34
|
+
"Guid",
|
|
35
|
+
"ByteString",
|
|
36
|
+
"XmlElement",
|
|
37
|
+
"NodeId",
|
|
38
|
+
"ExpandedNodeId",
|
|
39
|
+
"StatusCode",
|
|
40
|
+
"QualifiedName",
|
|
41
|
+
"LocalizedText",
|
|
42
|
+
"ExtensionObject",
|
|
43
|
+
"DataValue",
|
|
44
|
+
"Variant",
|
|
45
|
+
"DiagnosticInfo",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
assert [member.name for member in OpcUaDataTypes] == expected_names
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.mark.parametrize(
|
|
52
|
+
("member", "expected_value"),
|
|
53
|
+
[
|
|
54
|
+
(member_name, (member_name,))
|
|
55
|
+
for member_name in (
|
|
56
|
+
"Boolean",
|
|
57
|
+
"SByte",
|
|
58
|
+
"Byte",
|
|
59
|
+
"Int16",
|
|
60
|
+
"UInt16",
|
|
61
|
+
"Int32",
|
|
62
|
+
"UInt32",
|
|
63
|
+
"Int64",
|
|
64
|
+
"UInt64",
|
|
65
|
+
"Float",
|
|
66
|
+
"Double",
|
|
67
|
+
"String",
|
|
68
|
+
"DateTime",
|
|
69
|
+
"Guid",
|
|
70
|
+
"ByteString",
|
|
71
|
+
"XmlElement",
|
|
72
|
+
"NodeId",
|
|
73
|
+
"ExpandedNodeId",
|
|
74
|
+
"StatusCode",
|
|
75
|
+
"QualifiedName",
|
|
76
|
+
"LocalizedText",
|
|
77
|
+
"ExtensionObject",
|
|
78
|
+
"DataValue",
|
|
79
|
+
"Variant",
|
|
80
|
+
)
|
|
81
|
+
]
|
|
82
|
+
+ [("DiagnosticInfo", "DiagnosticInfo")],
|
|
83
|
+
)
|
|
84
|
+
def test_opcua_data_type_values(member: str, expected_value: tuple[str] | str) -> None:
|
|
85
|
+
assert OpcUaDataTypes[member].value == expected_value
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_opcua_data_types_can_be_looked_up_by_value() -> None:
|
|
89
|
+
assert OpcUaDataTypes(("Boolean",)) is OpcUaDataTypes.Boolean
|
|
90
|
+
assert OpcUaDataTypes("DiagnosticInfo") is OpcUaDataTypes.DiagnosticInfo
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Conversion of MTP supported automation datatypes."""
|
|
2
|
+
|
|
3
|
+
__copyright__ = (
|
|
4
|
+
"Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
|
|
5
|
+
)
|
|
6
|
+
__license__ = "MIT"
|
|
7
|
+
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
from enum import Enum
|
|
10
|
+
|
|
11
|
+
from opcua.ua import VariantType
|
|
12
|
+
|
|
13
|
+
import utils.type_conversion as tc
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class W3cXmlDataTypes(Enum):
|
|
17
|
+
"""Enumeration of W3C XML schema data types supported by MTP 2.0.
|
|
18
|
+
|
|
19
|
+
Each member of this enumeration maps a W3C XML schema data type to the
|
|
20
|
+
corresponding Python type, the matching ``opcua.ua.VariantType``
|
|
21
|
+
and an optional conversion function used to cast Python values to the
|
|
22
|
+
target representation when reading or writing MTP data items.
|
|
23
|
+
|
|
24
|
+
Fully Supported Attributes:
|
|
25
|
+
BOOLEAN,
|
|
26
|
+
BYTE, UNSIGNED_BYTE
|
|
27
|
+
SHORT, UNSIGNED_SHORT
|
|
28
|
+
INT, UNSIGNED_INT
|
|
29
|
+
LONG, UNSIGNED_LONG
|
|
30
|
+
FLOAT, DOUBLE
|
|
31
|
+
STRING
|
|
32
|
+
|
|
33
|
+
Partially Supported Attributes:
|
|
34
|
+
DATE, TIME, DATE_TIME
|
|
35
|
+
ANY_URI, TOKEN
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
BOOLEAN = (
|
|
39
|
+
"boolean",
|
|
40
|
+
bool,
|
|
41
|
+
VariantType.Boolean,
|
|
42
|
+
tc.cast_to_bool,
|
|
43
|
+
)
|
|
44
|
+
UNSIGNED_BYTE = (
|
|
45
|
+
"unsignedByte",
|
|
46
|
+
int,
|
|
47
|
+
VariantType.Byte,
|
|
48
|
+
tc.cast_to_ubyte,
|
|
49
|
+
)
|
|
50
|
+
UNSIGNED_SHORT = (
|
|
51
|
+
"unsignedShort",
|
|
52
|
+
int,
|
|
53
|
+
VariantType.UInt16,
|
|
54
|
+
tc.cast_to_ushort,
|
|
55
|
+
)
|
|
56
|
+
UNSIGNED_INT = (
|
|
57
|
+
"unsignedInt",
|
|
58
|
+
int,
|
|
59
|
+
VariantType.UInt32,
|
|
60
|
+
tc.cast_to_uint,
|
|
61
|
+
)
|
|
62
|
+
UNSIGNED_LONG = (
|
|
63
|
+
"unsignedLong",
|
|
64
|
+
int,
|
|
65
|
+
VariantType.Int64,
|
|
66
|
+
tc.cast_to_ulong,
|
|
67
|
+
)
|
|
68
|
+
BYTE = (
|
|
69
|
+
"byte",
|
|
70
|
+
int,
|
|
71
|
+
VariantType.SByte,
|
|
72
|
+
tc.cast_to_byte,
|
|
73
|
+
)
|
|
74
|
+
SHORT = (
|
|
75
|
+
"short",
|
|
76
|
+
int,
|
|
77
|
+
VariantType.Int16,
|
|
78
|
+
tc.cast_to_short,
|
|
79
|
+
)
|
|
80
|
+
INT = (
|
|
81
|
+
"int",
|
|
82
|
+
int,
|
|
83
|
+
VariantType.Int32,
|
|
84
|
+
tc.cast_to_int,
|
|
85
|
+
)
|
|
86
|
+
LONG = (
|
|
87
|
+
"long",
|
|
88
|
+
int,
|
|
89
|
+
VariantType.Int64,
|
|
90
|
+
tc.cast_to_long,
|
|
91
|
+
)
|
|
92
|
+
FLOAT = (
|
|
93
|
+
"float",
|
|
94
|
+
float,
|
|
95
|
+
VariantType.Float,
|
|
96
|
+
float,
|
|
97
|
+
)
|
|
98
|
+
DOUBLE = (
|
|
99
|
+
"double",
|
|
100
|
+
float,
|
|
101
|
+
VariantType.Double,
|
|
102
|
+
float,
|
|
103
|
+
)
|
|
104
|
+
STRING = (
|
|
105
|
+
"string",
|
|
106
|
+
str,
|
|
107
|
+
VariantType.String,
|
|
108
|
+
tc.cast_to_string,
|
|
109
|
+
)
|
|
110
|
+
DATE = (
|
|
111
|
+
"date",
|
|
112
|
+
None,
|
|
113
|
+
VariantType.DateTime,
|
|
114
|
+
None,
|
|
115
|
+
)
|
|
116
|
+
TIME = (
|
|
117
|
+
"time",
|
|
118
|
+
None,
|
|
119
|
+
VariantType.DateTime,
|
|
120
|
+
None,
|
|
121
|
+
)
|
|
122
|
+
DATE_TIME = (
|
|
123
|
+
"dateTime",
|
|
124
|
+
None,
|
|
125
|
+
VariantType.DateTime,
|
|
126
|
+
None,
|
|
127
|
+
)
|
|
128
|
+
ANY_URI = (
|
|
129
|
+
"anyURI",
|
|
130
|
+
None,
|
|
131
|
+
None,
|
|
132
|
+
None,
|
|
133
|
+
)
|
|
134
|
+
TOKEN = (
|
|
135
|
+
"token",
|
|
136
|
+
None,
|
|
137
|
+
None,
|
|
138
|
+
None,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def __init__(
|
|
142
|
+
self,
|
|
143
|
+
string_value: str,
|
|
144
|
+
python_type: type | None,
|
|
145
|
+
opcua_variant: VariantType | None,
|
|
146
|
+
converse_function: Callable[[tc.ANY_TYPE], tc.ANY_TYPE] | None,
|
|
147
|
+
) -> None:
|
|
148
|
+
"""Initialize a W3cXmlDataTypes enum member.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
string_value: The W3C XML schema name of the data type
|
|
152
|
+
(e.g. "boolean", "unsignedInt", a.s.o).
|
|
153
|
+
python_type: The corresponding Python type (e.g. `bool`,
|
|
154
|
+
`int`, `float`, and `str`). May be `None`
|
|
155
|
+
for XML types without a direct Python equivalent
|
|
156
|
+
(e.g. `anyURI`, `token`).
|
|
157
|
+
opcua_variant: The corresponding
|
|
158
|
+
`opcua.ua.VariantType` value. May be ``None`` for XML
|
|
159
|
+
types without a direct OPC UA mapping.
|
|
160
|
+
converse_function: A callable used to cast or convert a Python
|
|
161
|
+
value to the data type's expected Python representation.
|
|
162
|
+
May be ``None`` for types that do not require an explicit
|
|
163
|
+
conversion step.
|
|
164
|
+
"""
|
|
165
|
+
self.string_value = string_value
|
|
166
|
+
self.python_type = python_type
|
|
167
|
+
self.opcua_variant = opcua_variant
|
|
168
|
+
self.converse = converse_function
|
|
169
|
+
|
|
170
|
+
def __str__(self) -> str:
|
|
171
|
+
"""Return the W3C XML schema name of the data type as string.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
The XML schema name of the data type as stored in
|
|
175
|
+
``string_value`` (e.g. ``"boolean"``, ``"unsignedInt"``).
|
|
176
|
+
"""
|
|
177
|
+
return self.string_value
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Tests for W3C XML data types."""
|
|
2
|
+
|
|
3
|
+
__copyright__ = (
|
|
4
|
+
"Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
|
|
5
|
+
)
|
|
6
|
+
__license__ = "MIT"
|
|
7
|
+
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
from opcua.ua import VariantType
|
|
12
|
+
|
|
13
|
+
from mtppy.part5.definitions.w3c_xml_data_types import W3cXmlDataTypes
|
|
14
|
+
import utils.type_conversion as tc
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_w3c_xml_data_types_is_enum() -> None:
|
|
18
|
+
assert issubclass(W3cXmlDataTypes, Enum)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_w3c_xml_data_types_contains_all_supported_types() -> None:
|
|
22
|
+
expected_names = [
|
|
23
|
+
"BOOLEAN",
|
|
24
|
+
"UNSIGNED_BYTE",
|
|
25
|
+
"UNSIGNED_SHORT",
|
|
26
|
+
"UNSIGNED_INT",
|
|
27
|
+
"UNSIGNED_LONG",
|
|
28
|
+
"BYTE",
|
|
29
|
+
"SHORT",
|
|
30
|
+
"INT",
|
|
31
|
+
"LONG",
|
|
32
|
+
"FLOAT",
|
|
33
|
+
"DOUBLE",
|
|
34
|
+
"STRING",
|
|
35
|
+
"DATE",
|
|
36
|
+
"TIME",
|
|
37
|
+
"DATE_TIME",
|
|
38
|
+
"ANY_URI",
|
|
39
|
+
"TOKEN",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
assert [member.name for member in W3cXmlDataTypes] == expected_names
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.mark.parametrize(
|
|
46
|
+
("member", "string_value", "python_type", "opcua_variant"),
|
|
47
|
+
[
|
|
48
|
+
(W3cXmlDataTypes.BOOLEAN, "boolean", bool, VariantType.Boolean),
|
|
49
|
+
(
|
|
50
|
+
W3cXmlDataTypes.UNSIGNED_BYTE,
|
|
51
|
+
"unsignedByte",
|
|
52
|
+
int,
|
|
53
|
+
VariantType.Byte,
|
|
54
|
+
),
|
|
55
|
+
(
|
|
56
|
+
W3cXmlDataTypes.UNSIGNED_SHORT,
|
|
57
|
+
"unsignedShort",
|
|
58
|
+
int,
|
|
59
|
+
VariantType.UInt16,
|
|
60
|
+
),
|
|
61
|
+
(
|
|
62
|
+
W3cXmlDataTypes.UNSIGNED_INT,
|
|
63
|
+
"unsignedInt",
|
|
64
|
+
int,
|
|
65
|
+
VariantType.UInt32,
|
|
66
|
+
),
|
|
67
|
+
(
|
|
68
|
+
W3cXmlDataTypes.UNSIGNED_LONG,
|
|
69
|
+
"unsignedLong",
|
|
70
|
+
int,
|
|
71
|
+
VariantType.Int64,
|
|
72
|
+
),
|
|
73
|
+
(W3cXmlDataTypes.BYTE, "byte", int, VariantType.SByte),
|
|
74
|
+
(W3cXmlDataTypes.SHORT, "short", int, VariantType.Int16),
|
|
75
|
+
(W3cXmlDataTypes.INT, "int", int, VariantType.Int32),
|
|
76
|
+
(W3cXmlDataTypes.LONG, "long", int, VariantType.Int64),
|
|
77
|
+
(W3cXmlDataTypes.FLOAT, "float", float, VariantType.Float),
|
|
78
|
+
(W3cXmlDataTypes.DOUBLE, "double", float, VariantType.Double),
|
|
79
|
+
(W3cXmlDataTypes.STRING, "string", str, VariantType.String),
|
|
80
|
+
(W3cXmlDataTypes.DATE, "date", None, VariantType.DateTime),
|
|
81
|
+
(W3cXmlDataTypes.TIME, "time", None, VariantType.DateTime),
|
|
82
|
+
(W3cXmlDataTypes.DATE_TIME, "dateTime", None, VariantType.DateTime),
|
|
83
|
+
(W3cXmlDataTypes.ANY_URI, "anyURI", None, None),
|
|
84
|
+
(W3cXmlDataTypes.TOKEN, "token", None, None),
|
|
85
|
+
],
|
|
86
|
+
)
|
|
87
|
+
def test_w3c_xml_data_type_metadata(
|
|
88
|
+
member: W3cXmlDataTypes,
|
|
89
|
+
string_value: str,
|
|
90
|
+
python_type: type | None,
|
|
91
|
+
opcua_variant: VariantType | None,
|
|
92
|
+
) -> None:
|
|
93
|
+
assert member.string_value == string_value
|
|
94
|
+
assert member.python_type is python_type
|
|
95
|
+
assert member.opcua_variant is opcua_variant
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_w3c_xml_data_types_string_representation() -> None:
|
|
99
|
+
assert str(W3cXmlDataTypes.BOOLEAN) == "boolean"
|
|
100
|
+
assert str(W3cXmlDataTypes.DATE_TIME) == "dateTime"
|
|
101
|
+
assert str(W3cXmlDataTypes.ANY_URI) == "anyURI"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@pytest.mark.parametrize(
|
|
105
|
+
("member", "value", "expected_value"),
|
|
106
|
+
[
|
|
107
|
+
(W3cXmlDataTypes.BOOLEAN, 1, True),
|
|
108
|
+
(W3cXmlDataTypes.UNSIGNED_BYTE, 256, 255),
|
|
109
|
+
(W3cXmlDataTypes.UNSIGNED_SHORT, 65536, 65535),
|
|
110
|
+
(W3cXmlDataTypes.UNSIGNED_INT, 2**32, 2**32 - 1),
|
|
111
|
+
(W3cXmlDataTypes.UNSIGNED_LONG, 2**64, 2**64 - 1),
|
|
112
|
+
(W3cXmlDataTypes.BYTE, 128, 127),
|
|
113
|
+
(W3cXmlDataTypes.SHORT, 2**15, 2**15 - 1),
|
|
114
|
+
(W3cXmlDataTypes.INT, 2**31, 2**31 - 1),
|
|
115
|
+
(W3cXmlDataTypes.LONG, 2**63, 2**63 - 1),
|
|
116
|
+
(W3cXmlDataTypes.FLOAT, "1.5", 1.5),
|
|
117
|
+
(W3cXmlDataTypes.DOUBLE, "2.5", 2.5),
|
|
118
|
+
(W3cXmlDataTypes.STRING, 123, "123"),
|
|
119
|
+
],
|
|
120
|
+
)
|
|
121
|
+
def test_w3c_xml_data_type_conversions(
|
|
122
|
+
member: W3cXmlDataTypes, value: object, expected_value: object
|
|
123
|
+
) -> None:
|
|
124
|
+
assert member.converse is not None
|
|
125
|
+
assert member.converse(value) == expected_value
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_w3c_xml_data_types_without_conversion_are_unmapped() -> None:
|
|
129
|
+
for member in (
|
|
130
|
+
W3cXmlDataTypes.DATE,
|
|
131
|
+
W3cXmlDataTypes.TIME,
|
|
132
|
+
W3cXmlDataTypes.DATE_TIME,
|
|
133
|
+
W3cXmlDataTypes.ANY_URI,
|
|
134
|
+
W3cXmlDataTypes.TOKEN,
|
|
135
|
+
):
|
|
136
|
+
assert member.converse is None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_w3c_xml_data_types_can_be_looked_up_by_value() -> None:
|
|
140
|
+
assert (
|
|
141
|
+
W3cXmlDataTypes(("boolean", bool, VariantType.Boolean, tc.cast_to_bool))
|
|
142
|
+
is W3cXmlDataTypes.BOOLEAN
|
|
143
|
+
)
|
mtppy/part6/__init__.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Static AlarmSet.Base alarm descriptions."""
|
|
2
|
+
|
|
3
|
+
__copyright__ = (
|
|
4
|
+
"Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
|
|
5
|
+
)
|
|
6
|
+
__license__ = "MIT"
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
from mtppy.part3.data_assembly_set.data_assembly import DataAssembly
|
|
11
|
+
from mtppy.part5.definitions.w3c_xml_data_types import W3cXmlDataTypes
|
|
12
|
+
from mtppy.utils.data_item import DataItem
|
|
13
|
+
|
|
14
|
+
MIN_SEVERITY = 1
|
|
15
|
+
MAX_SEVERITY = 1000
|
|
16
|
+
_INTEGER_RANGES = {
|
|
17
|
+
W3cXmlDataTypes.BYTE: (-(2**7), (2**7) - 1),
|
|
18
|
+
W3cXmlDataTypes.UNSIGNED_BYTE: (0, (2**8) - 1),
|
|
19
|
+
W3cXmlDataTypes.SHORT: (-(2**15), (2**15) - 1),
|
|
20
|
+
W3cXmlDataTypes.UNSIGNED_SHORT: (0, (2**16) - 1),
|
|
21
|
+
W3cXmlDataTypes.INT: (-(2**31), (2**31) - 1),
|
|
22
|
+
W3cXmlDataTypes.UNSIGNED_INT: (0, (2**32) - 1),
|
|
23
|
+
W3cXmlDataTypes.LONG: (-(2**63), (2**63) - 1),
|
|
24
|
+
W3cXmlDataTypes.UNSIGNED_LONG: (0, (2**64) - 1),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True, slots=True)
|
|
29
|
+
class Alarm:
|
|
30
|
+
"""Describe a POL-evaluated AlarmSet.Base alarm.
|
|
31
|
+
|
|
32
|
+
The alarm links a value of an existing DataItem to static alarm metadata in
|
|
33
|
+
the generated MTP. It does not observe the DataItem or maintain alarm state
|
|
34
|
+
inside the PEA.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
tag_name: Unique alarm name within the PEA.
|
|
38
|
+
data_assembly: DataAssembly containing the trigger DataItem.
|
|
39
|
+
trigger: Existing DataItem used as the alarm trigger.
|
|
40
|
+
trigger_value: Value for which the POL considers the alarm active.
|
|
41
|
+
message: Operator-facing alarm message.
|
|
42
|
+
ack_required: Whether the POL should require acknowledgement.
|
|
43
|
+
severity: Alarm severity in the inclusive range from 1 to 1000.
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
TypeError: If an argument has an unsupported type.
|
|
47
|
+
ValueError: If text is empty, severity is out of range, or the trigger
|
|
48
|
+
does not belong to the DataAssembly.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
tag_name: str
|
|
52
|
+
data_assembly: DataAssembly
|
|
53
|
+
trigger: DataItem
|
|
54
|
+
trigger_value: object
|
|
55
|
+
message: str
|
|
56
|
+
ack_required: bool
|
|
57
|
+
severity: int
|
|
58
|
+
|
|
59
|
+
def __post_init__(self) -> None:
|
|
60
|
+
"""Validate the static alarm description."""
|
|
61
|
+
self.validate()
|
|
62
|
+
|
|
63
|
+
def validate(self) -> None:
|
|
64
|
+
"""Revalidate this alarm and its mutable DataItem reference.
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
TypeError: If a field or trigger type is unsupported.
|
|
68
|
+
ValueError: If a value is outside its permitted range or the trigger
|
|
69
|
+
no longer belongs to the configured DataAssembly.
|
|
70
|
+
"""
|
|
71
|
+
self._validate_text(self.tag_name, "tag_name")
|
|
72
|
+
self._validate_text(self.message, "message")
|
|
73
|
+
|
|
74
|
+
if not isinstance(self.data_assembly, DataAssembly):
|
|
75
|
+
msg = "data_assembly must be a DataAssembly"
|
|
76
|
+
raise TypeError(msg)
|
|
77
|
+
if not isinstance(self.trigger, DataItem):
|
|
78
|
+
msg = "trigger must be a DataItem"
|
|
79
|
+
raise TypeError(msg)
|
|
80
|
+
if not any(
|
|
81
|
+
self.data_assembly.get_item(key) is self.trigger
|
|
82
|
+
for key in self.data_assembly
|
|
83
|
+
):
|
|
84
|
+
msg = "trigger must belong to the configured DataAssembly"
|
|
85
|
+
raise ValueError(msg)
|
|
86
|
+
|
|
87
|
+
trigger_type = self.trigger.w3c_xml_data_type.python_type
|
|
88
|
+
if trigger_type not in (bool, int, str):
|
|
89
|
+
type_name = self.trigger.w3c_xml_data_type.string_value
|
|
90
|
+
msg = f"Alarm trigger type {type_name} is not supported"
|
|
91
|
+
raise TypeError(msg)
|
|
92
|
+
if type(self.trigger_value) is not trigger_type:
|
|
93
|
+
msg = "TriggerValue must have the exact type of the trigger DataItem"
|
|
94
|
+
raise TypeError(msg)
|
|
95
|
+
integer_range = _INTEGER_RANGES.get(self.trigger.w3c_xml_data_type)
|
|
96
|
+
if integer_range is not None and not (
|
|
97
|
+
integer_range[0] <= self.trigger_value <= integer_range[1]
|
|
98
|
+
):
|
|
99
|
+
msg = (
|
|
100
|
+
f"TriggerValue is outside the range of "
|
|
101
|
+
f"xs:{self.trigger.w3c_xml_data_type.string_value}"
|
|
102
|
+
)
|
|
103
|
+
raise ValueError(msg)
|
|
104
|
+
|
|
105
|
+
if type(self.ack_required) is not bool:
|
|
106
|
+
msg = "ack_required must be a boolean"
|
|
107
|
+
raise TypeError(msg)
|
|
108
|
+
if type(self.severity) is not int:
|
|
109
|
+
msg = "severity must be an integer and bool is not accepted"
|
|
110
|
+
raise TypeError(msg)
|
|
111
|
+
if not MIN_SEVERITY <= self.severity <= MAX_SEVERITY:
|
|
112
|
+
msg = f"severity must be between {MIN_SEVERITY} and {MAX_SEVERITY}"
|
|
113
|
+
raise ValueError(msg)
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def _validate_text(value: str, field_name: str) -> None:
|
|
117
|
+
"""Validate a required text field.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
value: Field value to validate.
|
|
121
|
+
field_name: Field name used in the error message.
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
TypeError: If the value is not a string.
|
|
125
|
+
ValueError: If the value contains no visible characters.
|
|
126
|
+
"""
|
|
127
|
+
if not isinstance(value, str):
|
|
128
|
+
msg = f"{field_name} must be a string"
|
|
129
|
+
raise TypeError(msg)
|
|
130
|
+
if not value.strip():
|
|
131
|
+
msg = f"{field_name} must not be empty"
|
|
132
|
+
raise ValueError(msg)
|