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.
Files changed (214) hide show
  1. examples/__init__.py +0 -0
  2. examples/minimal+pid.py +82 -0
  3. examples/minimal.py +32 -0
  4. examples/minimal_recipe.py +117 -0
  5. examples/pea_alarm_base.py +47 -0
  6. examples/pea_minimal.py +258 -0
  7. mtppy/__init__.py +1 -0
  8. mtppy/part1/__init__.py +4 -0
  9. mtppy/part1/empty_folder_test.py +7 -0
  10. mtppy/part3/__init__.py +4 -0
  11. mtppy/part3/data_assembly_set/__init__.py +4 -0
  12. mtppy/part3/data_assembly_set/active_elements/__init__.py +4 -0
  13. mtppy/part3/data_assembly_set/active_elements/active_element.py +22 -0
  14. mtppy/part3/data_assembly_set/active_elements/drives/__init__.py +4 -0
  15. mtppy/part3/data_assembly_set/active_elements/drives/ana_drv.py +446 -0
  16. mtppy/part3/data_assembly_set/active_elements/drives/ana_drv_test.py +567 -0
  17. mtppy/part3/data_assembly_set/active_elements/drives/bin_drv.py +365 -0
  18. mtppy/part3/data_assembly_set/active_elements/drives/bin_drv_test.py +503 -0
  19. mtppy/part3/data_assembly_set/active_elements/drives/mon_ana_drv.py +469 -0
  20. mtppy/part3/data_assembly_set/active_elements/drives/mon_ana_drv_test.py +418 -0
  21. mtppy/part3/data_assembly_set/active_elements/drives/mon_bin_drv.py +295 -0
  22. mtppy/part3/data_assembly_set/active_elements/drives/mon_bin_drv_test.py +265 -0
  23. mtppy/part3/data_assembly_set/active_elements/pid/__init__.py +4 -0
  24. mtppy/part3/data_assembly_set/active_elements/pid/pid_ctrl.py +333 -0
  25. mtppy/part3/data_assembly_set/active_elements/pid/pid_ctrl_test.py +156 -0
  26. mtppy/part3/data_assembly_set/active_elements/valves/__init__.py +4 -0
  27. mtppy/part3/data_assembly_set/active_elements/valves/ana_vlv.py +402 -0
  28. mtppy/part3/data_assembly_set/active_elements/valves/ana_vlv_test.py +732 -0
  29. mtppy/part3/data_assembly_set/active_elements/valves/bin_vlv.py +279 -0
  30. mtppy/part3/data_assembly_set/active_elements/valves/bin_vlv_test.py +585 -0
  31. mtppy/part3/data_assembly_set/active_elements/valves/mon_ana_vlv.py +402 -0
  32. mtppy/part3/data_assembly_set/active_elements/valves/mon_ana_vlv_test.py +270 -0
  33. mtppy/part3/data_assembly_set/active_elements/valves/mon_bin_vlv.py +286 -0
  34. mtppy/part3/data_assembly_set/active_elements/valves/mon_bin_vlv_test.py +224 -0
  35. mtppy/part3/data_assembly_set/base_functions/__init__.py +4 -0
  36. mtppy/part3/data_assembly_set/base_functions/operation_mode.py +252 -0
  37. mtppy/part3/data_assembly_set/base_functions/operation_mode_test.py +260 -0
  38. mtppy/part3/data_assembly_set/base_functions/oslevel.py +35 -0
  39. mtppy/part3/data_assembly_set/base_functions/source_mode.py +189 -0
  40. mtppy/part3/data_assembly_set/base_functions/source_mode_test.py +112 -0
  41. mtppy/part3/data_assembly_set/base_functions/wqc.py +75 -0
  42. mtppy/part3/data_assembly_set/base_functions/wqc_test.py +86 -0
  43. mtppy/part3/data_assembly_set/data_assembly.py +65 -0
  44. mtppy/part3/data_assembly_set/diagnostic_elements/__init__.py +4 -0
  45. mtppy/part3/data_assembly_set/diagnostic_elements/diagnostic_element.py +19 -0
  46. mtppy/part3/data_assembly_set/indicator_elements/__init__.py +4 -0
  47. mtppy/part3/data_assembly_set/indicator_elements/indicator_element.py +22 -0
  48. mtppy/part3/data_assembly_set/indicator_elements/views/__init__.py +4 -0
  49. mtppy/part3/data_assembly_set/indicator_elements/views/ana_view.py +68 -0
  50. mtppy/part3/data_assembly_set/indicator_elements/views/bin_view.py +73 -0
  51. mtppy/part3/data_assembly_set/indicator_elements/views/dint_view.py +76 -0
  52. mtppy/part3/data_assembly_set/indicator_elements/views/string_view.py +55 -0
  53. mtppy/part3/data_assembly_set/operation_elements/__init__.py +4 -0
  54. mtppy/part3/data_assembly_set/operation_elements/operation_element.py +19 -0
  55. mtppy/part3/data_assembly_set/pea_elements/__init__.py +4 -0
  56. mtppy/part3/data_assembly_set/pea_elements/pea_element.py +28 -0
  57. mtppy/part3/data_assembly_set/pea_elements/pea_information_label/__init__.py +4 -0
  58. mtppy/part3/data_assembly_set/pea_elements/pea_information_label/pea_information_label.py +231 -0
  59. mtppy/part3/data_assembly_set/pea_elements/web_server_url_info/__init__.py +4 -0
  60. mtppy/part3/data_assembly_set/pea_elements/web_server_url_info/web_server_url_info.py +42 -0
  61. mtppy/part3/definitions/__init__.py +4 -0
  62. mtppy/part3/definitions/mtp_access.py +15 -0
  63. mtppy/part4/__init__.py +4 -0
  64. mtppy/part4/automation_services/__init__.py +4 -0
  65. mtppy/part4/automation_services/base_functions/__init__.py +4 -0
  66. mtppy/part4/automation_services/base_functions/service_operation_mode.py +368 -0
  67. mtppy/part4/automation_services/base_functions/service_operation_mode_test.py +242 -0
  68. mtppy/part4/automation_services/base_functions/service_operator_interaction.py +62 -0
  69. mtppy/part4/automation_services/base_functions/service_source_mode.py +138 -0
  70. mtppy/part4/automation_services/base_functions/service_source_mode_test.py +179 -0
  71. mtppy/part4/automation_services/helpers/__init__.py +4 -0
  72. mtppy/part4/automation_services/helpers/command_en_control.py +330 -0
  73. mtppy/part4/automation_services/helpers/command_en_control_test.py +302 -0
  74. mtppy/part4/automation_services/helpers/state_machine.py +167 -0
  75. mtppy/part4/automation_services/helpers/state_machine_test.py +294 -0
  76. mtppy/part4/automation_services/helpers/thread_control.py +39 -0
  77. mtppy/part4/automation_services/helpers/thread_control_test.py +80 -0
  78. mtppy/part4/automation_services/procedure.py +111 -0
  79. mtppy/part4/automation_services/procedure_test.py +185 -0
  80. mtppy/part4/automation_services/service.py +463 -0
  81. mtppy/part4/automation_services/service_elements/__init__.py +4 -0
  82. mtppy/part4/automation_services/service_elements/parameter_elements/__init__.py +4 -0
  83. mtppy/part4/automation_services/service_elements/parameter_elements/ana_serv_param.py +108 -0
  84. mtppy/part4/automation_services/service_elements/parameter_elements/ana_serv_param_test.py +59 -0
  85. mtppy/part4/automation_services/service_elements/parameter_elements/bin_serv_param.py +70 -0
  86. mtppy/part4/automation_services/service_elements/parameter_elements/bin_serv_param_test.py +39 -0
  87. mtppy/part4/automation_services/service_elements/parameter_elements/common_serv_param.py +111 -0
  88. mtppy/part4/automation_services/service_elements/parameter_elements/dint_serv_param.py +77 -0
  89. mtppy/part4/automation_services/service_elements/parameter_elements/dint_serv_param_test.py +58 -0
  90. mtppy/part4/automation_services/service_elements/parameter_elements/parameter_element.py +175 -0
  91. mtppy/part4/automation_services/service_elements/parameter_elements/parameter_element_test.py +246 -0
  92. mtppy/part4/automation_services/service_elements/parameter_elements/string_serv_param.py +48 -0
  93. mtppy/part4/automation_services/service_elements/parameter_elements/string_serv_param_test.py +37 -0
  94. mtppy/part4/automation_services/service_elements/procedure_health_view.py +36 -0
  95. mtppy/part4/automation_services/service_elements/service_control.py +473 -0
  96. mtppy/part4/automation_services/service_elements/service_control_test.py +150 -0
  97. mtppy/part4/automation_services/service_elements/service_element.py +20 -0
  98. mtppy/part4/automation_services/service_test.py +407 -0
  99. mtppy/part4/definitions/__init__.py +4 -0
  100. mtppy/part4/definitions/command_codes.py +58 -0
  101. mtppy/part4/definitions/command_codes_test.py +44 -0
  102. mtppy/part4/definitions/state_codes.py +65 -0
  103. mtppy/part4/definitions/state_codes_test.py +44 -0
  104. mtppy/part4/process_values/__init__.py +4 -0
  105. mtppy/part4/process_values/base_functions/__init__.py +4 -0
  106. mtppy/part4/process_values/base_functions/vqc.py +48 -0
  107. mtppy/part4/process_values/input_elements/__init__.py +4 -0
  108. mtppy/part4/process_values/input_elements/ana_process_value_in.py +160 -0
  109. mtppy/part4/process_values/input_elements/bin_process_value_in.py +83 -0
  110. mtppy/part4/process_values/input_elements/dint_process_value_in.py +159 -0
  111. mtppy/part4/process_values/input_elements/input_element.py +40 -0
  112. mtppy/part4/process_values/input_elements/string_process_value_in.py +45 -0
  113. mtppy/part5/__init__.py +4 -0
  114. mtppy/part5/communication_object.py +99 -0
  115. mtppy/part5/communication_object_test.py +59 -0
  116. mtppy/part5/definitions/__init__.py +4 -0
  117. mtppy/part5/definitions/iec61131_data_types.py +31 -0
  118. mtppy/part5/definitions/iec61131_data_types_test.py +82 -0
  119. mtppy/part5/definitions/opcua_data_types.py +36 -0
  120. mtppy/part5/definitions/opcua_data_types_test.py +90 -0
  121. mtppy/part5/definitions/w3c_xml_data_types.py +177 -0
  122. mtppy/part5/definitions/w3c_xml_data_types_test.py +143 -0
  123. mtppy/part5/server_assembly_set/__init__.py +5 -0
  124. mtppy/part6/__init__.py +4 -0
  125. mtppy/part6/alarm_set/__init__.py +4 -0
  126. mtppy/part6/alarm_set/alarm.py +132 -0
  127. mtppy/part6/alarm_set/alarm_test.py +265 -0
  128. mtppy/pea/__init__.py +19 -0
  129. mtppy/pea/data_assembly_factory.py +124 -0
  130. mtppy/pea/data_assembly_factory_test.py +229 -0
  131. mtppy/pea/mtp_generator/__init__.py +4 -0
  132. mtppy/pea/mtp_generator/mtp_generator.py +1183 -0
  133. mtppy/pea/mtp_generator/mtp_generator_test.py +518 -0
  134. mtppy/pea/pea.py +139 -0
  135. mtppy/pea/pea_opcua_server/__init__.py +4 -0
  136. mtppy/pea/pea_opcua_server/marshalling.py +60 -0
  137. mtppy/pea/pea_opcua_server/opcua_options_provider/__init__.py +4 -0
  138. mtppy/pea/pea_opcua_server/opcua_options_provider/instances/basic_opcua_options_provider.py +50 -0
  139. mtppy/pea/pea_opcua_server/opcua_options_provider/opcua_options_provider.py +103 -0
  140. mtppy/pea/pea_opcua_server/pea_opcua_server.py +492 -0
  141. mtppy/pea/pea_opcua_server/subscription_list.py +55 -0
  142. mtppy/pea/pea_test.py +59 -0
  143. mtppy/pea/rmtp_pea.py +155 -0
  144. mtppy/pea/rmtp_pea_test.py +27 -0
  145. mtppy/pea/service_factory.py +178 -0
  146. mtppy/pea/service_factory_test.py +156 -0
  147. mtppy/pea/user_logic/__init__.py +4 -0
  148. mtppy/pea/user_logic/user_logic_pea.py +72 -0
  149. mtppy/pea/user_logic/user_logic_registry.py +106 -0
  150. mtppy/pea/user_logic/user_logic_registry_test.py +167 -0
  151. mtppy/pea/user_logic/user_logic_service.py +228 -0
  152. mtppy/pea/user_logic/user_logic_service_test.py +289 -0
  153. mtppy/utils/__init__.py +4 -0
  154. mtppy/utils/data_assembly_dict.py +61 -0
  155. mtppy/utils/data_assembly_dict_test.py +107 -0
  156. mtppy/utils/data_item.py +446 -0
  157. mtppy/utils/data_item_com_test.py +82 -0
  158. mtppy/utils/data_item_test.py +325 -0
  159. mtppy/utils/logging.py +23 -0
  160. mtppy/utils/logging_test.py +26 -0
  161. mtppy/utils/mtp_file_handling/__init__.py +4 -0
  162. mtppy/utils/mtp_file_handling/extract_manifest_from_mtp_file.py +22 -0
  163. mtppy/utils/mtp_file_handling/extract_mtp_file.py +22 -0
  164. mtppy/utils/mtp_file_handling/is_valid_mtp.py +22 -0
  165. mtppy/utils/mtp_file_handling/is_valid_path_for_manifest.py +15 -0
  166. mtppy/utils/mtp_file_handling/is_valid_path_to_manifest.py +17 -0
  167. mtppy/utils/mtp_file_handling/is_valid_path_to_mtp.py +17 -0
  168. mtppy/utils/mtp_file_handling/load_manifest_file.py +24 -0
  169. mtppy/utils/mtp_file_handling/mtp_reader/__init__.py +4 -0
  170. mtppy/utils/mtp_file_handling/mtp_reader/mtp_reader.py +103 -0
  171. mtppy/utils/mtp_file_handling/mtp_reader/mtp_reader_test.py +96 -0
  172. mtppy/utils/mtp_file_handling/mtp_reader/mtp_xml.py +226 -0
  173. mtppy/utils/mtp_file_handling/mtp_reader/mtp_xml_test.py +108 -0
  174. mtppy/utils/mtp_file_handling/mtp_writer/__init__.py +4 -0
  175. mtppy/utils/mtp_file_handling/mtp_writer/mtp_writer.py +77 -0
  176. mtppy/utils/mtp_file_handling/mtp_writer/ressources/get_content_types.py +10 -0
  177. mtppy/utils/mtp_file_handling/mtp_writer/ressources/get_rels.py +11 -0
  178. mtppy/utils/mtp_file_handling/mtp_writer/test_mtp_writer.py +25 -0
  179. mtppy/utils/mtp_file_handling/write_manifest_file.py +29 -0
  180. mtppy/utils/mtp_file_handling/write_manifest_file_test.py +22 -0
  181. mtppy/utils/mtp_file_handling/write_mtp_file.py +43 -0
  182. mtppy/utils/mtp_file_handling/write_mtp_file_test.py +22 -0
  183. mtppy/utils/testing.py +31 -0
  184. mtppy2_0-0.1.5.dist-info/METADATA +75 -0
  185. mtppy2_0-0.1.5.dist-info/RECORD +214 -0
  186. mtppy2_0-0.1.5.dist-info/WHEEL +5 -0
  187. mtppy2_0-0.1.5.dist-info/entry_points.txt +3 -0
  188. mtppy2_0-0.1.5.dist-info/licenses/LICENSE +21 -0
  189. mtppy2_0-0.1.5.dist-info/top_level.txt +4 -0
  190. tools/__init__.py +0 -0
  191. tools/create_bugreport.py +150 -0
  192. tools/inspect_mtp.py +110 -0
  193. utils/__init__.py +1 -0
  194. utils/file_handling/__init__.py +4 -0
  195. utils/file_handling/cleanup_folder.py +9 -0
  196. utils/file_handling/get_utc_foldername_with_ms.py +15 -0
  197. utils/file_handling/get_work_dir.py +9 -0
  198. utils/file_handling/is_valid_path_for_json.py +11 -0
  199. utils/file_handling/is_valid_path_to_json.py +17 -0
  200. utils/file_handling/load_json_file.py +18 -0
  201. utils/file_handling/read_file_from_file_archive.py +43 -0
  202. utils/file_handling/read_file_string.py +10 -0
  203. utils/file_handling/validate_json_data.py +28 -0
  204. utils/file_handling/write_json_file.py +23 -0
  205. utils/get_uuid.py +18 -0
  206. utils/get_uuid_test.py +17 -0
  207. utils/logging.py +100 -0
  208. utils/logging_test.py +67 -0
  209. utils/opcua/check_node.py +79 -0
  210. utils/resolve_unit/resolve_unit.py +11 -0
  211. utils/resolve_unit/unit_lib.py +2101 -0
  212. utils/resolve_unit/unit_lib_test.py +36 -0
  213. utils/type_conversion.py +236 -0
  214. 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)
@@ -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