sift-stack-py 0.3.2__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 (291) hide show
  1. google/__init__.py +1 -0
  2. google/api/__init__.py +0 -0
  3. google/api/annotations_pb2.py +27 -0
  4. google/api/annotations_pb2.pyi +29 -0
  5. google/api/annotations_pb2_grpc.py +4 -0
  6. google/api/annotations_pb2_grpc.pyi +30 -0
  7. google/api/field_behavior_pb2.py +30 -0
  8. google/api/field_behavior_pb2.pyi +175 -0
  9. google/api/field_behavior_pb2_grpc.py +4 -0
  10. google/api/field_behavior_pb2_grpc.pyi +30 -0
  11. google/api/http_pb2.py +31 -0
  12. google/api/http_pb2.pyi +433 -0
  13. google/api/http_pb2_grpc.py +4 -0
  14. google/api/http_pb2_grpc.pyi +30 -0
  15. protoc_gen_openapiv2/__init__.py +0 -0
  16. protoc_gen_openapiv2/options/__init__.py +0 -0
  17. protoc_gen_openapiv2/options/annotations_pb2.py +27 -0
  18. protoc_gen_openapiv2/options/annotations_pb2.pyi +48 -0
  19. protoc_gen_openapiv2/options/annotations_pb2_grpc.py +4 -0
  20. protoc_gen_openapiv2/options/annotations_pb2_grpc.pyi +17 -0
  21. protoc_gen_openapiv2/options/openapiv2_pb2.py +132 -0
  22. protoc_gen_openapiv2/options/openapiv2_pb2.pyi +1533 -0
  23. protoc_gen_openapiv2/options/openapiv2_pb2_grpc.py +4 -0
  24. protoc_gen_openapiv2/options/openapiv2_pb2_grpc.pyi +17 -0
  25. sift/__init__.py +0 -0
  26. sift/annotation_logs/__init__.py +0 -0
  27. sift/annotation_logs/v1/__init__.py +0 -0
  28. sift/annotation_logs/v1/annotation_logs_pb2.py +115 -0
  29. sift/annotation_logs/v1/annotation_logs_pb2.pyi +370 -0
  30. sift/annotation_logs/v1/annotation_logs_pb2_grpc.py +135 -0
  31. sift/annotation_logs/v1/annotation_logs_pb2_grpc.pyi +84 -0
  32. sift/annotations/__init__.py +0 -0
  33. sift/annotations/v1/__init__.py +0 -0
  34. sift/annotations/v1/annotations_pb2.py +180 -0
  35. sift/annotations/v1/annotations_pb2.pyi +539 -0
  36. sift/annotations/v1/annotations_pb2_grpc.py +237 -0
  37. sift/annotations/v1/annotations_pb2_grpc.pyi +144 -0
  38. sift/assets/__init__.py +0 -0
  39. sift/assets/v1/__init__.py +0 -0
  40. sift/assets/v1/assets_pb2.py +90 -0
  41. sift/assets/v1/assets_pb2.pyi +235 -0
  42. sift/assets/v1/assets_pb2_grpc.py +168 -0
  43. sift/assets/v1/assets_pb2_grpc.pyi +101 -0
  44. sift/calculated_channels/__init__.py +0 -0
  45. sift/calculated_channels/v1/__init__.py +0 -0
  46. sift/calculated_channels/v1/calculated_channels_pb2.py +99 -0
  47. sift/calculated_channels/v1/calculated_channels_pb2.pyi +280 -0
  48. sift/calculated_channels/v1/calculated_channels_pb2_grpc.py +101 -0
  49. sift/calculated_channels/v1/calculated_channels_pb2_grpc.pyi +64 -0
  50. sift/campaigns/__init__.py +0 -0
  51. sift/campaigns/v1/__init__.py +0 -0
  52. sift/campaigns/v1/campaigns_pb2.py +144 -0
  53. sift/campaigns/v1/campaigns_pb2.pyi +383 -0
  54. sift/campaigns/v1/campaigns_pb2_grpc.py +169 -0
  55. sift/campaigns/v1/campaigns_pb2_grpc.pyi +104 -0
  56. sift/channel_schemas/__init__.py +0 -0
  57. sift/channel_schemas/v1/__init__.py +0 -0
  58. sift/channel_schemas/v1/channel_schemas_pb2.py +69 -0
  59. sift/channel_schemas/v1/channel_schemas_pb2.pyi +117 -0
  60. sift/channel_schemas/v1/channel_schemas_pb2_grpc.py +101 -0
  61. sift/channel_schemas/v1/channel_schemas_pb2_grpc.pyi +64 -0
  62. sift/channels/__init__.py +0 -0
  63. sift/channels/v2/__init__.py +0 -0
  64. sift/channels/v2/channels_pb2.py +88 -0
  65. sift/channels/v2/channels_pb2.pyi +183 -0
  66. sift/channels/v2/channels_pb2_grpc.py +101 -0
  67. sift/channels/v2/channels_pb2_grpc.pyi +64 -0
  68. sift/common/__init__.py +0 -0
  69. sift/common/type/__init__.py +0 -0
  70. sift/common/type/v1/__init__.py +0 -0
  71. sift/common/type/v1/channel_bit_field_element_pb2.py +34 -0
  72. sift/common/type/v1/channel_bit_field_element_pb2.pyi +33 -0
  73. sift/common/type/v1/channel_bit_field_element_pb2_grpc.py +4 -0
  74. sift/common/type/v1/channel_bit_field_element_pb2_grpc.pyi +17 -0
  75. sift/common/type/v1/channel_data_type_pb2.py +29 -0
  76. sift/common/type/v1/channel_data_type_pb2.pyi +50 -0
  77. sift/common/type/v1/channel_data_type_pb2_grpc.py +4 -0
  78. sift/common/type/v1/channel_data_type_pb2_grpc.pyi +17 -0
  79. sift/common/type/v1/channel_enum_type_pb2.py +32 -0
  80. sift/common/type/v1/channel_enum_type_pb2.pyi +29 -0
  81. sift/common/type/v1/channel_enum_type_pb2_grpc.py +4 -0
  82. sift/common/type/v1/channel_enum_type_pb2_grpc.pyi +17 -0
  83. sift/common/type/v1/organization_pb2.py +27 -0
  84. sift/common/type/v1/organization_pb2.pyi +29 -0
  85. sift/common/type/v1/organization_pb2_grpc.py +4 -0
  86. sift/common/type/v1/organization_pb2_grpc.pyi +17 -0
  87. sift/common/type/v1/resource_identifier_pb2.py +46 -0
  88. sift/common/type/v1/resource_identifier_pb2.pyi +145 -0
  89. sift/common/type/v1/resource_identifier_pb2_grpc.py +4 -0
  90. sift/common/type/v1/resource_identifier_pb2_grpc.pyi +17 -0
  91. sift/common/type/v1/user_pb2.py +33 -0
  92. sift/common/type/v1/user_pb2.pyi +36 -0
  93. sift/common/type/v1/user_pb2_grpc.py +4 -0
  94. sift/common/type/v1/user_pb2_grpc.pyi +17 -0
  95. sift/data/__init__.py +0 -0
  96. sift/data/v1/__init__.py +0 -0
  97. sift/data/v1/data_pb2.py +212 -0
  98. sift/data/v1/data_pb2.pyi +745 -0
  99. sift/data/v1/data_pb2_grpc.py +67 -0
  100. sift/data/v1/data_pb2_grpc.pyi +44 -0
  101. sift/ingest/__init__.py +0 -0
  102. sift/ingest/v1/__init__.py +0 -0
  103. sift/ingest/v1/ingest_pb2.py +35 -0
  104. sift/ingest/v1/ingest_pb2.pyi +118 -0
  105. sift/ingest/v1/ingest_pb2_grpc.py +66 -0
  106. sift/ingest/v1/ingest_pb2_grpc.pyi +41 -0
  107. sift/ingestion_configs/__init__.py +0 -0
  108. sift/ingestion_configs/v1/__init__.py +0 -0
  109. sift/ingestion_configs/v1/ingestion_configs_pb2.py +115 -0
  110. sift/ingestion_configs/v1/ingestion_configs_pb2.pyi +332 -0
  111. sift/ingestion_configs/v1/ingestion_configs_pb2_grpc.py +203 -0
  112. sift/ingestion_configs/v1/ingestion_configs_pb2_grpc.pyi +124 -0
  113. sift/notifications/__init__.py +0 -0
  114. sift/notifications/v1/__init__.py +0 -0
  115. sift/notifications/v1/notifications_pb2.py +64 -0
  116. sift/notifications/v1/notifications_pb2.pyi +225 -0
  117. sift/notifications/v1/notifications_pb2_grpc.py +101 -0
  118. sift/notifications/v1/notifications_pb2_grpc.pyi +64 -0
  119. sift/ping/__init__.py +0 -0
  120. sift/ping/v1/__init__.py +0 -0
  121. sift/ping/v1/ping_pb2.py +38 -0
  122. sift/ping/v1/ping_pb2.pyi +36 -0
  123. sift/ping/v1/ping_pb2_grpc.py +66 -0
  124. sift/ping/v1/ping_pb2_grpc.pyi +41 -0
  125. sift/remote_files/__init__.py +0 -0
  126. sift/remote_files/v1/__init__.py +0 -0
  127. sift/remote_files/v1/remote_files_pb2.py +174 -0
  128. sift/remote_files/v1/remote_files_pb2.pyi +472 -0
  129. sift/remote_files/v1/remote_files_pb2_grpc.py +271 -0
  130. sift/remote_files/v1/remote_files_pb2_grpc.pyi +164 -0
  131. sift/report_templates/__init__.py +0 -0
  132. sift/report_templates/v1/__init__.py +0 -0
  133. sift/report_templates/v1/report_templates_pb2.py +146 -0
  134. sift/report_templates/v1/report_templates_pb2.pyi +381 -0
  135. sift/report_templates/v1/report_templates_pb2_grpc.py +169 -0
  136. sift/report_templates/v1/report_templates_pb2_grpc.pyi +104 -0
  137. sift/reports/__init__.py +0 -0
  138. sift/reports/v1/__init__.py +0 -0
  139. sift/reports/v1/reports_pb2.py +193 -0
  140. sift/reports/v1/reports_pb2.pyi +562 -0
  141. sift/reports/v1/reports_pb2_grpc.py +205 -0
  142. sift/reports/v1/reports_pb2_grpc.pyi +136 -0
  143. sift/rule_evaluation/__init__.py +0 -0
  144. sift/rule_evaluation/v1/__init__.py +0 -0
  145. sift/rule_evaluation/v1/rule_evaluation_pb2.py +89 -0
  146. sift/rule_evaluation/v1/rule_evaluation_pb2.pyi +263 -0
  147. sift/rule_evaluation/v1/rule_evaluation_pb2_grpc.py +101 -0
  148. sift/rule_evaluation/v1/rule_evaluation_pb2_grpc.pyi +64 -0
  149. sift/rules/__init__.py +0 -0
  150. sift/rules/v1/__init__.py +0 -0
  151. sift/rules/v1/rules_pb2.py +420 -0
  152. sift/rules/v1/rules_pb2.pyi +1355 -0
  153. sift/rules/v1/rules_pb2_grpc.py +577 -0
  154. sift/rules/v1/rules_pb2_grpc.pyi +351 -0
  155. sift/runs/__init__.py +0 -0
  156. sift/runs/v2/__init__.py +0 -0
  157. sift/runs/v2/runs_pb2.py +150 -0
  158. sift/runs/v2/runs_pb2.pyi +413 -0
  159. sift/runs/v2/runs_pb2_grpc.py +271 -0
  160. sift/runs/v2/runs_pb2_grpc.pyi +164 -0
  161. sift/saved_searches/__init__.py +0 -0
  162. sift/saved_searches/v1/__init__.py +0 -0
  163. sift/saved_searches/v1/saved_searches_pb2.py +144 -0
  164. sift/saved_searches/v1/saved_searches_pb2.pyi +385 -0
  165. sift/saved_searches/v1/saved_searches_pb2_grpc.py +237 -0
  166. sift/saved_searches/v1/saved_searches_pb2_grpc.pyi +144 -0
  167. sift/tags/__init__.py +0 -0
  168. sift/tags/v1/__init__.py +0 -0
  169. sift/tags/v1/tags_pb2.py +49 -0
  170. sift/tags/v1/tags_pb2.pyi +71 -0
  171. sift/tags/v1/tags_pb2_grpc.py +4 -0
  172. sift/tags/v1/tags_pb2_grpc.pyi +17 -0
  173. sift/users/__init__.py +0 -0
  174. sift/users/v2/__init__.py +0 -0
  175. sift/users/v2/users_pb2.py +61 -0
  176. sift/users/v2/users_pb2.pyi +142 -0
  177. sift/users/v2/users_pb2_grpc.py +135 -0
  178. sift/users/v2/users_pb2_grpc.pyi +84 -0
  179. sift/views/__init__.py +0 -0
  180. sift/views/v1/__init__.py +0 -0
  181. sift/views/v1/views_pb2.py +130 -0
  182. sift/views/v1/views_pb2.pyi +466 -0
  183. sift/views/v1/views_pb2_grpc.py +305 -0
  184. sift/views/v1/views_pb2_grpc.pyi +184 -0
  185. sift_grafana/py.typed +0 -0
  186. sift_grafana/sift_query_model.py +64 -0
  187. sift_py/__init__.py +923 -0
  188. sift_py/_internal/__init__.py +5 -0
  189. sift_py/_internal/cel.py +18 -0
  190. sift_py/_internal/channel.py +42 -0
  191. sift_py/_internal/convert/__init__.py +3 -0
  192. sift_py/_internal/convert/json.py +24 -0
  193. sift_py/_internal/convert/protobuf.py +34 -0
  194. sift_py/_internal/convert/timestamp.py +9 -0
  195. sift_py/_internal/test_util/__init__.py +0 -0
  196. sift_py/_internal/test_util/channel.py +136 -0
  197. sift_py/_internal/test_util/fn.py +14 -0
  198. sift_py/_internal/test_util/server_interceptor.py +62 -0
  199. sift_py/_internal/time.py +48 -0
  200. sift_py/_internal/user.py +39 -0
  201. sift_py/data/__init__.py +171 -0
  202. sift_py/data/_channel.py +38 -0
  203. sift_py/data/_deserialize.py +208 -0
  204. sift_py/data/_deserialize_test.py +134 -0
  205. sift_py/data/_service_test.py +276 -0
  206. sift_py/data/_validate.py +10 -0
  207. sift_py/data/error.py +5 -0
  208. sift_py/data/query.py +299 -0
  209. sift_py/data/service.py +497 -0
  210. sift_py/data_import/__init__.py +130 -0
  211. sift_py/data_import/_config.py +167 -0
  212. sift_py/data_import/_config_test.py +166 -0
  213. sift_py/data_import/_csv_test.py +395 -0
  214. sift_py/data_import/_status_test.py +176 -0
  215. sift_py/data_import/_tdms_test.py +238 -0
  216. sift_py/data_import/ch10.py +157 -0
  217. sift_py/data_import/config.py +19 -0
  218. sift_py/data_import/csv.py +259 -0
  219. sift_py/data_import/status.py +113 -0
  220. sift_py/data_import/tdms.py +206 -0
  221. sift_py/data_import/tempfile.py +30 -0
  222. sift_py/data_import/time_format.py +39 -0
  223. sift_py/error.py +11 -0
  224. sift_py/file_attachment/__init__.py +88 -0
  225. sift_py/file_attachment/_internal/__init__.py +0 -0
  226. sift_py/file_attachment/_internal/download.py +13 -0
  227. sift_py/file_attachment/_internal/upload.py +100 -0
  228. sift_py/file_attachment/_service_test.py +161 -0
  229. sift_py/file_attachment/entity.py +30 -0
  230. sift_py/file_attachment/metadata.py +107 -0
  231. sift_py/file_attachment/service.py +142 -0
  232. sift_py/grpc/__init__.py +15 -0
  233. sift_py/grpc/_async_interceptors/__init__.py +0 -0
  234. sift_py/grpc/_async_interceptors/base.py +72 -0
  235. sift_py/grpc/_async_interceptors/metadata.py +36 -0
  236. sift_py/grpc/_interceptors/__init__.py +0 -0
  237. sift_py/grpc/_interceptors/base.py +61 -0
  238. sift_py/grpc/_interceptors/context.py +25 -0
  239. sift_py/grpc/_interceptors/metadata.py +33 -0
  240. sift_py/grpc/_retry.py +70 -0
  241. sift_py/grpc/keepalive.py +34 -0
  242. sift_py/grpc/transport.py +250 -0
  243. sift_py/grpc/transport_test.py +170 -0
  244. sift_py/ingestion/__init__.py +6 -0
  245. sift_py/ingestion/_internal/__init__.py +6 -0
  246. sift_py/ingestion/_internal/channel.py +12 -0
  247. sift_py/ingestion/_internal/error.py +10 -0
  248. sift_py/ingestion/_internal/ingest.py +350 -0
  249. sift_py/ingestion/_internal/ingest_test.py +357 -0
  250. sift_py/ingestion/_internal/ingestion_config.py +130 -0
  251. sift_py/ingestion/_internal/run.py +46 -0
  252. sift_py/ingestion/_service_test.py +478 -0
  253. sift_py/ingestion/buffer.py +189 -0
  254. sift_py/ingestion/channel.py +422 -0
  255. sift_py/ingestion/config/__init__.py +3 -0
  256. sift_py/ingestion/config/telemetry.py +281 -0
  257. sift_py/ingestion/config/telemetry_test.py +405 -0
  258. sift_py/ingestion/config/yaml/__init__.py +0 -0
  259. sift_py/ingestion/config/yaml/error.py +44 -0
  260. sift_py/ingestion/config/yaml/load.py +126 -0
  261. sift_py/ingestion/config/yaml/spec.py +58 -0
  262. sift_py/ingestion/config/yaml/test_load.py +25 -0
  263. sift_py/ingestion/flow.py +73 -0
  264. sift_py/ingestion/manager.py +99 -0
  265. sift_py/ingestion/rule/__init__.py +4 -0
  266. sift_py/ingestion/rule/config.py +11 -0
  267. sift_py/ingestion/service.py +237 -0
  268. sift_py/py.typed +0 -0
  269. sift_py/report_templates/__init__.py +0 -0
  270. sift_py/report_templates/_config_test.py +34 -0
  271. sift_py/report_templates/_service_test.py +94 -0
  272. sift_py/report_templates/config.py +36 -0
  273. sift_py/report_templates/service.py +171 -0
  274. sift_py/rest.py +29 -0
  275. sift_py/rule/__init__.py +0 -0
  276. sift_py/rule/_config_test.py +109 -0
  277. sift_py/rule/_service_test.py +168 -0
  278. sift_py/rule/config.py +229 -0
  279. sift_py/rule/service.py +484 -0
  280. sift_py/yaml/__init__.py +0 -0
  281. sift_py/yaml/_channel_test.py +169 -0
  282. sift_py/yaml/_rule_test.py +207 -0
  283. sift_py/yaml/channel.py +224 -0
  284. sift_py/yaml/report_templates.py +73 -0
  285. sift_py/yaml/rule.py +321 -0
  286. sift_py/yaml/utils.py +15 -0
  287. sift_stack_py-0.3.2.dist-info/LICENSE +7 -0
  288. sift_stack_py-0.3.2.dist-info/METADATA +109 -0
  289. sift_stack_py-0.3.2.dist-info/RECORD +291 -0
  290. sift_stack_py-0.3.2.dist-info/WHEEL +5 -0
  291. sift_stack_py-0.3.2.dist-info/top_level.txt +5 -0
@@ -0,0 +1,167 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict, List, Optional, Type, Union
4
+
5
+ from pydantic import BaseModel, ConfigDict, field_validator, model_validator
6
+ from pydantic_core import PydanticCustomError
7
+ from typing_extensions import Self
8
+
9
+ from sift_py.data_import.time_format import TimeFormatType
10
+ from sift_py.ingestion.channel import ChannelBitFieldElement, ChannelDataType, ChannelEnumType
11
+
12
+
13
+ class ConfigBaseModel(BaseModel):
14
+ """
15
+ Specialized BaseMode that forbids extra fields.
16
+ """
17
+
18
+ model_config = ConfigDict(extra="forbid")
19
+
20
+
21
+ class CsvConfigImpl(ConfigBaseModel):
22
+ """
23
+ Defines the CSV config spec.
24
+ """
25
+
26
+ asset_name: str
27
+ run_name: str = ""
28
+ run_id: str = ""
29
+ first_data_row: int
30
+ time_column: TimeColumn
31
+ data_columns: Dict[int, DataColumn]
32
+
33
+ @model_validator(mode="after")
34
+ def validate_config(self) -> Self:
35
+ if not self.data_columns:
36
+ raise PydanticCustomError("invalid_config_error", "Empty 'data_columns'")
37
+ return self
38
+
39
+
40
+ class EnumType(ConfigBaseModel, ChannelEnumType):
41
+ """
42
+ Defines an enum entry in the CSV config.
43
+ """
44
+
45
+
46
+ class BitFieldElement(ConfigBaseModel, ChannelBitFieldElement):
47
+ """
48
+ Defines a bit field element entry in the CSV config.
49
+ """
50
+
51
+
52
+ class TimeColumn(ConfigBaseModel):
53
+ """
54
+ Defines a time column entry in the CSV config.
55
+ """
56
+
57
+ format: Union[str, TimeFormatType]
58
+ column_number: int
59
+ relative_start_time: Optional[str] = None
60
+
61
+ @field_validator("format", mode="before")
62
+ @classmethod
63
+ def convert_format(cls, raw: Union[str, TimeFormatType]) -> str:
64
+ """
65
+ Converts the provided format value to a string.
66
+ """
67
+ if isinstance(raw, TimeFormatType):
68
+ return raw.as_human_str()
69
+ elif isinstance(raw, str):
70
+ value = TimeFormatType.from_str(raw)
71
+ if value is not None:
72
+ return value.as_human_str()
73
+
74
+ raise PydanticCustomError("invalid_config_error", f"Invalid time format: {raw}.")
75
+
76
+ @model_validator(mode="after")
77
+ def validate_time(self) -> Self:
78
+ """
79
+ Validates the provided time format.
80
+ """
81
+ format = TimeFormatType.from_str(self.format) # type: ignore
82
+ if format is None:
83
+ raise PydanticCustomError(
84
+ "invalid_config_error", f"Invalid time format: {self.format}."
85
+ )
86
+
87
+ if format.is_relative():
88
+ if self.relative_start_time is None:
89
+ raise PydanticCustomError("invalid_config_error", "Missing 'relative_start_time'")
90
+ else:
91
+ if self.relative_start_time is not None:
92
+ raise PydanticCustomError(
93
+ "invalid_config_error",
94
+ "'relative_start_time' specified for non relative time format.",
95
+ )
96
+
97
+ return self
98
+
99
+
100
+ class DataColumn(ConfigBaseModel):
101
+ """
102
+ Defines a data column entry in the CSV config.
103
+ """
104
+
105
+ name: str
106
+ data_type: Union[str, ChannelDataType, Type]
107
+ component: str = ""
108
+ units: str = ""
109
+ description: str = ""
110
+ # Only valid if data_type is "CHANNEL_DATA_TYPE_ENUM".
111
+ enum_types: List[EnumType] = []
112
+ # Only valid if data_type is "CHANNEL_DATA_TYPE_BIT_FIELD"
113
+ bit_field_elements: List[BitFieldElement] = []
114
+
115
+ @field_validator("data_type", mode="before")
116
+ @classmethod
117
+ def convert_data_type(cls, raw: Union[str, ChannelDataType, Type]) -> str:
118
+ """
119
+ Converts the provided data_type value to a string.
120
+ """
121
+ if isinstance(raw, type):
122
+ if raw == int:
123
+ return ChannelDataType.INT_64.as_human_str(api_format=True)
124
+ elif raw == float:
125
+ return ChannelDataType.DOUBLE.as_human_str(api_format=True)
126
+ elif raw == str:
127
+ return ChannelDataType.STRING.as_human_str(api_format=True)
128
+ elif raw == bool:
129
+ return ChannelDataType.BOOL.as_human_str(api_format=True)
130
+ elif isinstance(raw, ChannelDataType):
131
+ return raw.as_human_str(api_format=True)
132
+ elif isinstance(raw, str):
133
+ value = ChannelDataType.from_str(raw)
134
+ if value is not None:
135
+ return value.as_human_str(api_format=True)
136
+
137
+ raise PydanticCustomError("invalid_config_error", f"Invalid data_type: {raw}.")
138
+
139
+ @model_validator(mode="after")
140
+ def validate_enums(self) -> Self:
141
+ """
142
+ Validates the enum configuration.
143
+ """
144
+ data_type = ChannelDataType.from_str(self.data_type) # type: ignore
145
+ if self.enum_types:
146
+ if data_type != ChannelDataType.ENUM:
147
+ raise PydanticCustomError(
148
+ "invalid_config_error",
149
+ f"Enums can only be specified with the CHANNEL_DATA_TYPE_ENUM data type. {self.name} is {self.data_type}",
150
+ )
151
+
152
+ return self
153
+
154
+ @model_validator(mode="after")
155
+ def validate_bit_fields(self) -> Self:
156
+ """
157
+ Validates the bit field configuration.
158
+ """
159
+ data_type = ChannelDataType.from_str(self.data_type) # type: ignore
160
+ if self.bit_field_elements:
161
+ if data_type != ChannelDataType.BIT_FIELD:
162
+ raise PydanticCustomError(
163
+ "invalid_config_error",
164
+ f"Bit fields can only be specified with the CHANNEL_DATA_TYPE_BIT_FIELD data type. {self.name} is {self.data_type}",
165
+ )
166
+
167
+ return self
@@ -0,0 +1,166 @@
1
+ import pytest
2
+
3
+ from sift_py.data_import.config import CsvConfig
4
+ from sift_py.data_import.time_format import TimeFormatType
5
+ from sift_py.ingestion.channel import ChannelDataType
6
+
7
+
8
+ @pytest.fixture
9
+ def csv_config_data():
10
+ return {
11
+ "asset_name": "test_asset",
12
+ "first_data_row": 2,
13
+ "time_column": {
14
+ "format": "TIME_FORMAT_ABSOLUTE_DATETIME",
15
+ "column_number": 1,
16
+ },
17
+ "data_columns": {
18
+ 1: {
19
+ "name": "channel",
20
+ "data_type": "CHANNEL_DATA_TYPE_INT_32",
21
+ }
22
+ },
23
+ }
24
+
25
+
26
+ def test_empty_data_columns(csv_config_data: dict):
27
+ csv_config_data["data_columns"] = {}
28
+ with pytest.raises(Exception, match="Empty 'data_columns'"):
29
+ CsvConfig(csv_config_data)
30
+
31
+
32
+ def test_data_column_validation(csv_config_data: dict):
33
+ csv_config_data["data_columns"] = {
34
+ 1: {
35
+ "name": "channel",
36
+ "data_type": "INVALID_DATA_TYPE",
37
+ }
38
+ }
39
+ with pytest.raises(Exception, match="Invalid data_type:"):
40
+ CsvConfig(csv_config_data)
41
+
42
+ csv_config_data["data_columns"] = {1: {"name": "channel", "data_type": complex}}
43
+ with pytest.raises(Exception, match="Invalid data_type:"):
44
+ CsvConfig(csv_config_data)
45
+
46
+ csv_config_data["data_columns"] = {
47
+ 1: {"name": "channel_bool", "data_type": ChannelDataType.BOOL},
48
+ 2: {"name": "channel_double", "data_type": ChannelDataType.DOUBLE},
49
+ 3: {"name": "channel_int", "data_type": ChannelDataType.INT_64},
50
+ 4: {"name": "channel_str", "data_type": ChannelDataType.STRING},
51
+ }
52
+ CsvConfig(csv_config_data)
53
+
54
+
55
+ def test_enums(csv_config_data: dict):
56
+ csv_config_data["data_columns"] = {
57
+ 1: {
58
+ "name": "channel",
59
+ "data_type": "CHANNEL_DATA_TYPE_INT_32",
60
+ "enum_types": [
61
+ {"key": 1, "name": "value_1"},
62
+ {"key": 2, "name": "value_2"},
63
+ ],
64
+ }
65
+ }
66
+ with pytest.raises(Exception, match="Enums can only be specified"):
67
+ CsvConfig(csv_config_data)
68
+
69
+ csv_config_data["data_columns"] = {
70
+ 1: {
71
+ "name": "channel",
72
+ "data_type": "CHANNEL_DATA_TYPE_ENUM",
73
+ "enum_types": [
74
+ {"key": 1, "name": "value_1", "extra_key": "value"},
75
+ {"key": 2, "name": "value_2"},
76
+ ],
77
+ }
78
+ }
79
+ with pytest.raises(Exception, match="validation error"):
80
+ CsvConfig(csv_config_data)
81
+
82
+ csv_config_data["data_columns"] = {
83
+ 1: {
84
+ "name": "channel",
85
+ "data_type": "CHANNEL_DATA_TYPE_ENUM",
86
+ "enum_types": [
87
+ {"key": 1, "name": "value_1"},
88
+ {"key": 2, "name": "value_2"},
89
+ ],
90
+ }
91
+ }
92
+ CsvConfig(csv_config_data)
93
+
94
+
95
+ def test_bit_field(csv_config_data: dict):
96
+ csv_config_data["data_columns"] = {
97
+ 1: {
98
+ "name": "channel",
99
+ "data_type": "CHANNEL_DATA_TYPE_INT_32",
100
+ "bit_field_elements": [
101
+ {"index": 1, "name": "bit_field_name_1", "bit_count": 4},
102
+ ],
103
+ }
104
+ }
105
+ with pytest.raises(Exception, match="Bit fields can only be specified"):
106
+ CsvConfig(csv_config_data)
107
+
108
+ csv_config_data["data_columns"] = {
109
+ 1: {
110
+ "name": "channel",
111
+ "data_type": "CHANNEL_DATA_TYPE_INT_32",
112
+ "bit_field_elements": [
113
+ {
114
+ "index": 1,
115
+ "name": "bit_field_name_1",
116
+ "bit_count": 4,
117
+ "extra_key": "value",
118
+ },
119
+ ],
120
+ }
121
+ }
122
+ with pytest.raises(Exception, match="validation error"):
123
+ CsvConfig(csv_config_data)
124
+
125
+ csv_config_data["data_columns"] = {
126
+ 1: {
127
+ "name": "channel",
128
+ "data_type": "CHANNEL_DATA_TYPE_BIT_FIELD",
129
+ "bit_field_elements": [
130
+ {"index": 1, "name": "bit_field_name_1", "bit_count": 4},
131
+ ],
132
+ }
133
+ }
134
+ CsvConfig(csv_config_data)
135
+
136
+
137
+ def test_time_column(csv_config_data: dict):
138
+ csv_config_data["time_column"] = {
139
+ "format": "INVALID_TIME_FORMAT",
140
+ "column_number": 1,
141
+ }
142
+ with pytest.raises(Exception, match="Invalid time format"):
143
+ CsvConfig(csv_config_data)
144
+
145
+ csv_config_data["time_column"] = {
146
+ "format": "TIME_FORMAT_RELATIVE_SECONDS",
147
+ "column_number": 1,
148
+ }
149
+ with pytest.raises(Exception, match="Missing 'relative_start_time'"):
150
+ CsvConfig(csv_config_data)
151
+
152
+ csv_config_data["time_column"] = {
153
+ "format": "TIME_FORMAT_ABSOLUTE_UNIX_SECONDS",
154
+ "column_number": 1,
155
+ "relative_start_time": "100",
156
+ }
157
+ with pytest.raises(
158
+ Exception, match="'relative_start_time' specified for non relative time format."
159
+ ):
160
+ CsvConfig(csv_config_data)
161
+
162
+ csv_config_data["time_column"] = {
163
+ "format": TimeFormatType.ABSOLUTE_DATETIME,
164
+ "column_number": 1,
165
+ }
166
+ CsvConfig(csv_config_data)