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,36 @@
|
|
|
1
|
+
"""Tests for the unit catalog enum."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from utils.resolve_unit.unit_lib import Units
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_kelvin_metadata():
|
|
10
|
+
kelvin: Any = getattr(Units, "KELVIN")
|
|
11
|
+
assert kelvin.code == 1000
|
|
12
|
+
assert kelvin.name == "KELVIN"
|
|
13
|
+
assert kelvin.symbol == "K"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_unit_is_enum():
|
|
17
|
+
assert issubclass(Units, Enum)
|
|
18
|
+
assert Units(1000) is getattr(Units, "KELVIN")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_unit_is_accessible_as_attribute():
|
|
22
|
+
assert Units.DEGREE_CELSIUS.code == 1001
|
|
23
|
+
assert Units.DEGREE_CELSIUS.symbol == "°C"
|
|
24
|
+
assert Units.DEGREE_CELSIUS.name == "DEGREE_CELSIUS"
|
|
25
|
+
assert Units.DEGREE_CELSIUS.de == "Grad Celsius"
|
|
26
|
+
assert Units.DEGREE_CELSIUS.en == "Degrees Celsius"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_unit_names_are_explicit_and_stable():
|
|
30
|
+
assert getattr(Units, "DEGREE_CELSIUS").name == "DEGREE_CELSIUS"
|
|
31
|
+
assert getattr(Units, "SQUARE_METER").name == "SQUARE_METER"
|
|
32
|
+
assert getattr(Units, "NAUTICAL_MILE").name == "NAUTICAL_MILE"
|
|
33
|
+
assert getattr(Units, "MICROMETER").name == "MICROMETER"
|
|
34
|
+
assert getattr(Units, "PER_SECOND").name == "PER_SECOND"
|
|
35
|
+
assert all(unit.name.isidentifier() for unit in Units)
|
|
36
|
+
assert len({unit.name for unit in Units}) == len(Units)
|
utils/type_conversion.py
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""**type_conversion** collects utility functions that cast values to basic types used
|
|
2
|
+
in automation.
|
|
3
|
+
|
|
4
|
+
OPC UA, MTP, IEC 61131 and other automation standards have quite
|
|
5
|
+
specialized data types, which are not always directly supported by
|
|
6
|
+
Python, for instance signed/unsigned integer types of different
|
|
7
|
+
sizes (UNSIGNED SHORT != SHORT, INT != SHORT), or strings with a
|
|
8
|
+
maximum length.
|
|
9
|
+
|
|
10
|
+
The collected functions help to convert/cast values to the
|
|
11
|
+
appropriate python types, ensuring compatibility with these standards.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
__copyright__ = (
|
|
15
|
+
"Copyright (c) 2026 Dresden University of Technology, Process-to-Order Group"
|
|
16
|
+
)
|
|
17
|
+
__license__ = "MIT"
|
|
18
|
+
|
|
19
|
+
from typing import (
|
|
20
|
+
LiteralString,
|
|
21
|
+
SupportsBytes,
|
|
22
|
+
SupportsFloat,
|
|
23
|
+
SupportsIndex,
|
|
24
|
+
SupportsInt,
|
|
25
|
+
)
|
|
26
|
+
from numbers import Real
|
|
27
|
+
|
|
28
|
+
STRING_TYPE = str | bytes
|
|
29
|
+
NUMERIC_TYPE = SupportsFloat | SupportsBytes | SupportsInt | LiteralString
|
|
30
|
+
ANY_TYPE = STRING_TYPE | NUMERIC_TYPE
|
|
31
|
+
|
|
32
|
+
# Define general constants for integer clamps
|
|
33
|
+
INT8_MIN = -(2**7) # -128
|
|
34
|
+
INT8_MAX = (2**7) - 1 # 127
|
|
35
|
+
UINT8_MAX = (2**8) - 1 # 255
|
|
36
|
+
|
|
37
|
+
INT16_MIN = -(2**15) # -32768
|
|
38
|
+
INT16_MAX = (2**15) - 1 # 32767
|
|
39
|
+
UINT16_MAX = (2**16) - 1 # 65535
|
|
40
|
+
|
|
41
|
+
INT32_MIN = -(2**31) # -2147483648
|
|
42
|
+
INT32_MAX = (2**31) - 1 # 2147483647
|
|
43
|
+
UINT32_MAX = (2**32) - 1 # 4294967295
|
|
44
|
+
|
|
45
|
+
INT64_MIN = -(2**63) # -9223372036854775808
|
|
46
|
+
INT64_MAX = (2**63) - 1 # 9223372036854775807
|
|
47
|
+
UINT64_MAX = (2**64) - 1 # 18446744073709551615
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def cast_to_string(
|
|
51
|
+
value: object,
|
|
52
|
+
max_len: SupportsIndex | None = None,
|
|
53
|
+
) -> str:
|
|
54
|
+
"""Casts a value to a string, optionally truncating it to a maximum length.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
value: The value to be cast to a string.
|
|
58
|
+
max_len: The maximum length of the resulting string.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
The string representation of the value, optionally truncated.
|
|
62
|
+
|
|
63
|
+
"""
|
|
64
|
+
s = str(value)
|
|
65
|
+
if max_len is not None:
|
|
66
|
+
s = s[: int(max_len)]
|
|
67
|
+
return s
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def cast_to_bool(value: object) -> bool:
|
|
71
|
+
"""Casts canonical boolean values without treating arbitrary strings as true."""
|
|
72
|
+
if isinstance(value, bool):
|
|
73
|
+
return value
|
|
74
|
+
if isinstance(value, Real):
|
|
75
|
+
return bool(value)
|
|
76
|
+
if isinstance(value, str):
|
|
77
|
+
normalized_value = value.strip().lower()
|
|
78
|
+
if normalized_value in ("true", "1"):
|
|
79
|
+
return True
|
|
80
|
+
if normalized_value in ("false", "0"):
|
|
81
|
+
return False
|
|
82
|
+
raise ValueError(f"Invalid boolean value: {value!r}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def cast_to_int(value: SupportsInt) -> int:
|
|
86
|
+
"""Casts a value to an integer.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
value: The value to be cast to an integer.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
The integer representation of the value.
|
|
93
|
+
|
|
94
|
+
"""
|
|
95
|
+
int_value = int(value)
|
|
96
|
+
return max(INT32_MIN, min(INT32_MAX, int_value))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def cast_to_uint(value: SupportsInt) -> int:
|
|
100
|
+
"""Casts a value to an unsigned integer, ensuring it falls within the range of 0 to
|
|
101
|
+
4294967295.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
value: The value to be cast to an unsigned integer.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
The unsigned integer representation, clamped to the valid range.
|
|
108
|
+
|
|
109
|
+
"""
|
|
110
|
+
int_value = int(value)
|
|
111
|
+
return max(0, min(UINT32_MAX, int_value))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def cast_to_short(value: SupportsInt) -> int:
|
|
115
|
+
"""Casts a value to a short integer, ensuring it falls within the range of -32768
|
|
116
|
+
to 32767.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
value: The value to be cast to a short integer.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
The signed short integer representation of the value, clamped to the valid
|
|
123
|
+
range.
|
|
124
|
+
|
|
125
|
+
"""
|
|
126
|
+
int_value = cast_to_int(value)
|
|
127
|
+
return max(INT16_MIN, min(INT16_MAX, int_value))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def cast_to_ushort(value: SupportsInt) -> int:
|
|
131
|
+
"""Casts value to unsigned short integer, ensuring it falls within the range of
|
|
132
|
+
0 to 65535.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
value: The value to be cast to an unsigned short integer.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
The unsigned short integer representation, clamped to the valid range.
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
int_value = cast_to_int(value)
|
|
142
|
+
return max(0, min(UINT16_MAX, int_value))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def cast_to_long(value: SupportsInt) -> int:
|
|
146
|
+
"""Casts value to long integer, ensuring it falls within the range of
|
|
147
|
+
INT64_MIN to INT64_MAX (-9223372036854775808 to 9223372036854775807).
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
value: The value to be cast to a long integer.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
The signed integer representation of the value, clamped to the valid range.
|
|
154
|
+
|
|
155
|
+
"""
|
|
156
|
+
int_value = int(value)
|
|
157
|
+
return max(INT64_MIN, min(INT64_MAX, int_value))
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def cast_to_ulong(value: SupportsInt) -> int:
|
|
161
|
+
"""Casts value to unsigned long integer, ensuring it falls within the range of
|
|
162
|
+
0 to UINT64_MAX (18446744073709551615).
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
value: The value to be cast to an unsigned long integer.
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
The unsigned long integer representation, clamped to the valid range.
|
|
169
|
+
|
|
170
|
+
"""
|
|
171
|
+
int_value = int(value)
|
|
172
|
+
return max(0, min(UINT64_MAX, int_value))
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def cast_to_byte(value: SupportsInt) -> int:
|
|
176
|
+
"""Casts value to byte, ensuring it falls within the range of
|
|
177
|
+
INT8_MIN to INT8_MAX (-128 to 127).
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
value: The value to be cast to a byte.
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
The byte representation of the value, clamped to the valid range.
|
|
184
|
+
|
|
185
|
+
"""
|
|
186
|
+
int_val = int(value)
|
|
187
|
+
return max(INT8_MIN, min(INT8_MAX, int_val))
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def cast_to_ubyte(value: SupportsInt) -> int:
|
|
191
|
+
"""Casts value to unsigned byte, ensuring it falls within the range of
|
|
192
|
+
0 to UINT8_MAX (255).
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
value: The value to be cast to an unsigned byte.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
The unsigned byte representation of the value, clamped to the valid range.
|
|
199
|
+
|
|
200
|
+
"""
|
|
201
|
+
int_val = int(value)
|
|
202
|
+
return max(0, min(UINT8_MAX, int_val))
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def clamp(
|
|
206
|
+
value: float,
|
|
207
|
+
limit: tuple[float, float],
|
|
208
|
+
reset: tuple[float, float] | None = None,
|
|
209
|
+
) -> float:
|
|
210
|
+
"""Clamp a value between a lower and an upper bound.
|
|
211
|
+
|
|
212
|
+
When ``value`` is below the lower bound or above the upper bound, it is
|
|
213
|
+
replaced by the corresponding element of ``reset``. If ``reset`` is not
|
|
214
|
+
provided, the bounds themselves are used as the replacement values.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
value: The value to clamp.
|
|
218
|
+
limit: A ``(min, max)`` pair defining the inclusive bounds.
|
|
219
|
+
reset: An optional ``(min, max)`` pair returned when
|
|
220
|
+
``value`` is outside the bounds. Defaults to ``limit``.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
The clamped value, or the matching fallback bound.
|
|
224
|
+
|
|
225
|
+
Example:
|
|
226
|
+
clamp(value,[0,100],[0,0]) - generates a saw-tooth when fed with a ramp
|
|
227
|
+
clamp(value,[0,0]) - clamps the value between upper and lower bound
|
|
228
|
+
|
|
229
|
+
"""
|
|
230
|
+
if not reset:
|
|
231
|
+
reset = limit
|
|
232
|
+
if value < limit[0]:
|
|
233
|
+
return reset[0]
|
|
234
|
+
if value > limit[1]:
|
|
235
|
+
return reset[1]
|
|
236
|
+
return value
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Test suite for type conversion helpers."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from utils import type_conversion as tc
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_cast_to_string():
|
|
9
|
+
assert tc.cast_to_string(123) == "123"
|
|
10
|
+
assert tc.cast_to_string(b"value") == "b'value'"
|
|
11
|
+
assert tc.cast_to_string("abcdef", max_len=3) == "abc"
|
|
12
|
+
assert tc.cast_to_string("abcdef", max_len=None) == "abcdef"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.mark.parametrize(
|
|
16
|
+
("value", "expected"),
|
|
17
|
+
[
|
|
18
|
+
(True, True),
|
|
19
|
+
(False, False),
|
|
20
|
+
(1, True),
|
|
21
|
+
(0, False),
|
|
22
|
+
(" true ", True),
|
|
23
|
+
("FALSE", False),
|
|
24
|
+
("1", True),
|
|
25
|
+
("0", False),
|
|
26
|
+
],
|
|
27
|
+
)
|
|
28
|
+
def test_cast_to_bool_accepts_canonical_values(value, expected):
|
|
29
|
+
assert tc.cast_to_bool(value) is expected
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@pytest.mark.parametrize("value", ["yes", "no", "", None])
|
|
33
|
+
def test_cast_to_bool_rejects_non_canonical_values(value):
|
|
34
|
+
with pytest.raises(ValueError, match="Invalid boolean value"):
|
|
35
|
+
tc.cast_to_bool(value)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.mark.parametrize(
|
|
39
|
+
("converter", "value", "expected"),
|
|
40
|
+
[
|
|
41
|
+
(tc.cast_to_int, 12.9, 12),
|
|
42
|
+
(tc.cast_to_uint, 12, 12),
|
|
43
|
+
(tc.cast_to_short, 12, 12),
|
|
44
|
+
(tc.cast_to_ushort, 12, 12),
|
|
45
|
+
(tc.cast_to_long, 12, 12),
|
|
46
|
+
(tc.cast_to_ulong, 12, 12),
|
|
47
|
+
(tc.cast_to_byte, 12, 12),
|
|
48
|
+
(tc.cast_to_ubyte, 12, 12),
|
|
49
|
+
],
|
|
50
|
+
)
|
|
51
|
+
def test_integer_converters_accept_normal_values(converter, value, expected):
|
|
52
|
+
assert converter(value) == expected
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@pytest.mark.parametrize(
|
|
56
|
+
("converter", "value", "expected"),
|
|
57
|
+
[
|
|
58
|
+
(tc.cast_to_int, tc.INT32_MIN - 1, tc.INT32_MIN),
|
|
59
|
+
(tc.cast_to_int, tc.INT32_MAX + 1, tc.INT32_MAX),
|
|
60
|
+
(tc.cast_to_uint, -1, 0),
|
|
61
|
+
(tc.cast_to_uint, tc.UINT32_MAX + 1, tc.UINT32_MAX),
|
|
62
|
+
(tc.cast_to_short, tc.INT16_MIN - 1, tc.INT16_MIN),
|
|
63
|
+
(tc.cast_to_short, tc.INT16_MAX + 1, tc.INT16_MAX),
|
|
64
|
+
(tc.cast_to_ushort, -1, 0),
|
|
65
|
+
(tc.cast_to_ushort, tc.UINT16_MAX + 1, tc.UINT16_MAX),
|
|
66
|
+
(tc.cast_to_long, tc.INT64_MIN - 1, tc.INT64_MIN),
|
|
67
|
+
(tc.cast_to_long, tc.INT64_MAX + 1, tc.INT64_MAX),
|
|
68
|
+
(tc.cast_to_ulong, -1, 0),
|
|
69
|
+
(tc.cast_to_ulong, tc.UINT64_MAX + 1, tc.UINT64_MAX),
|
|
70
|
+
(tc.cast_to_byte, tc.INT8_MIN - 1, tc.INT8_MIN),
|
|
71
|
+
(tc.cast_to_byte, tc.INT8_MAX + 1, tc.INT8_MAX),
|
|
72
|
+
(tc.cast_to_ubyte, -1, 0),
|
|
73
|
+
(tc.cast_to_ubyte, tc.UINT8_MAX + 1, tc.UINT8_MAX),
|
|
74
|
+
],
|
|
75
|
+
)
|
|
76
|
+
def test_integer_converters_clamp_out_of_range_values(converter, value, expected):
|
|
77
|
+
assert converter(value) == expected
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@pytest.mark.parametrize(
|
|
81
|
+
"converter",
|
|
82
|
+
[
|
|
83
|
+
tc.cast_to_int,
|
|
84
|
+
tc.cast_to_uint,
|
|
85
|
+
tc.cast_to_short,
|
|
86
|
+
tc.cast_to_ushort,
|
|
87
|
+
tc.cast_to_long,
|
|
88
|
+
tc.cast_to_ulong,
|
|
89
|
+
tc.cast_to_byte,
|
|
90
|
+
tc.cast_to_ubyte,
|
|
91
|
+
],
|
|
92
|
+
)
|
|
93
|
+
def test_integer_converters_reject_non_numeric_strings(converter):
|
|
94
|
+
with pytest.raises(ValueError):
|
|
95
|
+
converter("not a number")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_clamp_keeps_values_inside_limits():
|
|
99
|
+
assert tc.clamp(50, (0, 100)) == 50
|
|
100
|
+
assert tc.clamp(0, (0, 100)) == 0
|
|
101
|
+
assert tc.clamp(100, (0, 100)) == 100
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@pytest.mark.parametrize(
|
|
105
|
+
("value", "limit", "reset", "expected"),
|
|
106
|
+
[
|
|
107
|
+
(-1, (0, 100), None, 0),
|
|
108
|
+
(101, (0, 100), None, 100),
|
|
109
|
+
(-1, (0, 100), (-10, 110), -10),
|
|
110
|
+
(101, (0, 100), (-10, 110), 110),
|
|
111
|
+
],
|
|
112
|
+
)
|
|
113
|
+
def test_clamp_replaces_values_outside_limits(value, limit, reset, expected):
|
|
114
|
+
assert tc.clamp(value, limit, reset) == expected
|