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,208 @@
1
+ from copy import deepcopy
2
+ from enum import Enum
3
+ from typing import List, Tuple, cast
4
+
5
+ from google.protobuf.any_pb2 import Any
6
+ from sift.data.v1.data_pb2 import (
7
+ BitFieldValues,
8
+ BoolValues,
9
+ DoubleValues,
10
+ EnumValues,
11
+ FloatValues,
12
+ Int32Values,
13
+ Int64Values,
14
+ Metadata,
15
+ StringValues,
16
+ Uint32Values,
17
+ Uint64Values,
18
+ )
19
+
20
+ from sift_py._internal.time import to_timestamp_nanos
21
+ from sift_py.data._channel import ChannelTimeSeries
22
+ from sift_py.error import SiftError
23
+ from sift_py.ingestion.channel import ChannelDataType
24
+
25
+
26
+ class ChannelValues(Enum):
27
+ DOUBLE_VALUES = "sift.data.v1.DoubleValues"
28
+ FLOAT_VALUES = "sift.data.v1.FloatValues"
29
+ STRING_VALUES = "sift.data.v1.StringValues"
30
+ ENUM_VALUES = "sift.data.v1.EnumValues"
31
+ BIT_FIELD_VALUES = "sift.data.v1.BitFieldValues"
32
+ BOOL_VALUES = "sift.data.v1.BoolValues"
33
+ INT32_VALUES = "sift.data.v1.Int32Values"
34
+ INT64_VALUES = "sift.data.v1.Int64Values"
35
+ UINT32_VALUES = "sift.data.v1.Uint32Values"
36
+ UINT64_VALUES = "sift.data.v1.Uint64Values"
37
+
38
+
39
+ def try_deserialize_channel_data(channel_values: Any) -> List[Tuple[Metadata, ChannelTimeSeries]]:
40
+ if ChannelValues.DOUBLE_VALUES.value in channel_values.type_url:
41
+ double_values = cast(DoubleValues, DoubleValues.FromString(channel_values.value))
42
+ metadata = double_values.metadata
43
+
44
+ time_column = []
45
+ double_value_column = []
46
+
47
+ for v in double_values.values:
48
+ time_column.append(to_timestamp_nanos(v.timestamp))
49
+ double_value_column.append(v.value)
50
+
51
+ time_series = ChannelTimeSeries(
52
+ ChannelDataType.from_pb(metadata.data_type), time_column, double_value_column
53
+ )
54
+ return [(metadata, time_series)]
55
+
56
+ elif ChannelValues.FLOAT_VALUES.value in channel_values.type_url:
57
+ float_values = cast(FloatValues, FloatValues.FromString(channel_values.value))
58
+ metadata = float_values.metadata
59
+
60
+ time_column = []
61
+ float_value_column = []
62
+
63
+ for float_v in float_values.values:
64
+ time_column.append(to_timestamp_nanos(float_v.timestamp))
65
+ float_value_column.append(float_v.value)
66
+
67
+ time_series = ChannelTimeSeries(
68
+ ChannelDataType.from_pb(metadata.data_type), time_column, float_value_column
69
+ )
70
+ return [(metadata, time_series)]
71
+
72
+ elif ChannelValues.STRING_VALUES.value in channel_values.type_url:
73
+ string_values = cast(StringValues, StringValues.FromString(channel_values.value))
74
+ metadata = string_values.metadata
75
+
76
+ time_column = []
77
+ string_value_column = []
78
+
79
+ for string_v in string_values.values:
80
+ time_column.append(to_timestamp_nanos(string_v.timestamp))
81
+ string_value_column.append(string_v.value)
82
+
83
+ time_series = ChannelTimeSeries(
84
+ ChannelDataType.from_pb(metadata.data_type), time_column, string_value_column
85
+ )
86
+ return [(metadata, time_series)]
87
+
88
+ elif ChannelValues.ENUM_VALUES.value in channel_values.type_url:
89
+ enum_values = cast(EnumValues, EnumValues.FromString(channel_values.value))
90
+ metadata = enum_values.metadata
91
+
92
+ time_column = []
93
+ enum_value_column = []
94
+
95
+ for enum_v in enum_values.values:
96
+ time_column.append(to_timestamp_nanos(enum_v.timestamp))
97
+ enum_value_column.append(enum_v.value)
98
+
99
+ time_series = ChannelTimeSeries(
100
+ ChannelDataType.from_pb(metadata.data_type), time_column, enum_value_column
101
+ )
102
+ return [(metadata, time_series)]
103
+
104
+ elif ChannelValues.BOOL_VALUES.value in channel_values.type_url:
105
+ bool_values = cast(BoolValues, BoolValues.FromString(channel_values.value))
106
+ metadata = bool_values.metadata
107
+
108
+ time_column = []
109
+ bool_value_column = []
110
+
111
+ for bool_v in bool_values.values:
112
+ time_column.append(to_timestamp_nanos(bool_v.timestamp))
113
+ bool_value_column.append(bool_v.value)
114
+
115
+ time_series = ChannelTimeSeries(
116
+ ChannelDataType.from_pb(metadata.data_type), time_column, bool_value_column
117
+ )
118
+ return [(metadata, time_series)]
119
+
120
+ elif ChannelValues.INT32_VALUES.value in channel_values.type_url:
121
+ int32_values = cast(Int32Values, Int32Values.FromString(channel_values.value))
122
+ metadata = int32_values.metadata
123
+
124
+ time_column = []
125
+ int32_value_column = []
126
+
127
+ for int32_v in int32_values.values:
128
+ time_column.append(to_timestamp_nanos(int32_v.timestamp))
129
+ int32_value_column.append(int32_v.value)
130
+
131
+ time_series = ChannelTimeSeries(
132
+ ChannelDataType.from_pb(metadata.data_type), time_column, int32_value_column
133
+ )
134
+ return [(metadata, time_series)]
135
+
136
+ elif ChannelValues.INT64_VALUES.value in channel_values.type_url:
137
+ int64_values = cast(Int64Values, Int64Values.FromString(channel_values.value))
138
+ metadata = int64_values.metadata
139
+
140
+ time_column = []
141
+ int64_value_column = []
142
+
143
+ for int64_v in int64_values.values:
144
+ time_column.append(to_timestamp_nanos(int64_v.timestamp))
145
+ int64_value_column.append(int64_v.value)
146
+
147
+ time_series = ChannelTimeSeries(
148
+ ChannelDataType.from_pb(metadata.data_type), time_column, int64_value_column
149
+ )
150
+ return [(metadata, time_series)]
151
+
152
+ elif ChannelValues.UINT32_VALUES.value in channel_values.type_url:
153
+ uint32_values = cast(Uint32Values, Uint32Values.FromString(channel_values.value))
154
+ metadata = uint32_values.metadata
155
+
156
+ time_column = []
157
+ uint32_value_column = []
158
+
159
+ for uint32_v in uint32_values.values:
160
+ time_column.append(to_timestamp_nanos(uint32_v.timestamp))
161
+ uint32_value_column.append(uint32_v.value)
162
+
163
+ time_series = ChannelTimeSeries(
164
+ ChannelDataType.from_pb(metadata.data_type), time_column, uint32_value_column
165
+ )
166
+ return [(metadata, time_series)]
167
+
168
+ elif ChannelValues.UINT64_VALUES.value in channel_values.type_url:
169
+ uint64_values = cast(Uint64Values, Uint64Values.FromString(channel_values.value))
170
+ metadata = uint64_values.metadata
171
+
172
+ time_column = []
173
+ uint64_value_column = []
174
+
175
+ for uint64_v in uint64_values.values:
176
+ time_column.append(to_timestamp_nanos(uint64_v.timestamp))
177
+ uint64_value_column.append(uint64_v.value)
178
+
179
+ time_series = ChannelTimeSeries(
180
+ ChannelDataType.from_pb(metadata.data_type), time_column, uint64_value_column
181
+ )
182
+ return [(metadata, time_series)]
183
+
184
+ elif ChannelValues.BIT_FIELD_VALUES.value in channel_values.type_url:
185
+ bit_field_values = cast(BitFieldValues, BitFieldValues.FromString(channel_values.value))
186
+ metadata = bit_field_values.metadata
187
+ data_type = ChannelDataType.from_pb(metadata.data_type)
188
+ channel_name = metadata.channel.name
189
+
190
+ parsed_data: List[Tuple[Metadata, ChannelTimeSeries]] = []
191
+
192
+ for bit_field_element in bit_field_values.values:
193
+ md_copy = deepcopy(bit_field_values.metadata)
194
+ md_copy.channel.name = f"{channel_name}.{bit_field_element.name}"
195
+
196
+ time_column = []
197
+ bit_field_el_column = []
198
+
199
+ for bf_v in bit_field_element.values:
200
+ time_column.append(to_timestamp_nanos(bf_v.timestamp))
201
+ bit_field_el_column.append(bf_v.value)
202
+
203
+ time_series = ChannelTimeSeries(data_type, time_column, bit_field_el_column)
204
+ parsed_data.append((md_copy, time_series))
205
+
206
+ return parsed_data
207
+
208
+ raise SiftError(f"Received an unknown channel-type '{channel_values.type_url}'.")
@@ -0,0 +1,134 @@
1
+ from google.protobuf.any_pb2 import Any
2
+ from sift.common.type.v1.channel_bit_field_element_pb2 import ChannelBitFieldElement
3
+ from sift.common.type.v1.channel_data_type_pb2 import (
4
+ CHANNEL_DATA_TYPE_BIT_FIELD,
5
+ CHANNEL_DATA_TYPE_DOUBLE,
6
+ )
7
+ from sift.data.v1.data_pb2 import (
8
+ BitFieldElementValues,
9
+ BitFieldValue,
10
+ BitFieldValues,
11
+ DoubleValue,
12
+ DoubleValues,
13
+ Metadata,
14
+ )
15
+
16
+ from sift_py._internal.time import to_timestamp_nanos, to_timestamp_pb
17
+ from sift_py.data._deserialize import try_deserialize_channel_data
18
+
19
+
20
+ def test_try_deserialize_channel_data_double():
21
+ metadata = Metadata(
22
+ data_type=CHANNEL_DATA_TYPE_DOUBLE, channel=Metadata.Channel(name="double-channel")
23
+ )
24
+
25
+ time_a = "2024-07-04T18:09:08.555-07:00"
26
+ time_b = "2024-07-04T18:09:09.555-07:00"
27
+
28
+ double_values = DoubleValues(
29
+ metadata=metadata,
30
+ values=[
31
+ DoubleValue(
32
+ timestamp=to_timestamp_pb(time_a),
33
+ value=10,
34
+ ),
35
+ DoubleValue(
36
+ timestamp=to_timestamp_pb(time_b),
37
+ value=11,
38
+ ),
39
+ ],
40
+ )
41
+
42
+ raw_values = Any()
43
+ raw_values.Pack(double_values)
44
+
45
+ deserialized_data = try_deserialize_channel_data(raw_values)
46
+
47
+ assert len(deserialized_data) == 1
48
+
49
+ metadata, time_series = deserialized_data[0]
50
+
51
+ assert metadata.data_type == CHANNEL_DATA_TYPE_DOUBLE
52
+ assert metadata.channel.name == "double-channel"
53
+ assert len(time_series.time_column) == 2
54
+ assert len(time_series.value_column) == 2
55
+ assert time_series.value_column[0] == 10
56
+ assert time_series.value_column[1] == 11
57
+ assert time_series.time_column[0] == to_timestamp_nanos(time_a)
58
+ assert time_series.time_column[1] == to_timestamp_nanos(time_b)
59
+
60
+
61
+ def test_try_deserialize_channel_data_bit_field_elements():
62
+ metadata = Metadata(
63
+ data_type=CHANNEL_DATA_TYPE_BIT_FIELD,
64
+ channel=Metadata.Channel(
65
+ name="gpio",
66
+ bit_field_elements=[
67
+ ChannelBitFieldElement(
68
+ name="12v",
69
+ index=0,
70
+ bit_count=4,
71
+ ),
72
+ ChannelBitFieldElement(
73
+ name="heater",
74
+ index=4,
75
+ bit_count=4,
76
+ ),
77
+ ],
78
+ ),
79
+ )
80
+
81
+ time_a = "2024-07-04T18:09:08.555-07:00"
82
+
83
+ value_a = int("10000001", 2)
84
+ value_b = int("11110001", 2)
85
+
86
+ bit_field_values = BitFieldValues(
87
+ metadata=metadata,
88
+ values=[
89
+ BitFieldElementValues(
90
+ name="12v",
91
+ values=[
92
+ BitFieldValue(
93
+ timestamp=to_timestamp_pb(time_a),
94
+ value=value_a,
95
+ )
96
+ ],
97
+ ),
98
+ BitFieldElementValues(
99
+ name="heater",
100
+ values=[
101
+ BitFieldValue(
102
+ timestamp=to_timestamp_pb(time_a),
103
+ value=value_b,
104
+ )
105
+ ],
106
+ ),
107
+ ],
108
+ )
109
+
110
+ raw_values = Any()
111
+ raw_values.Pack(bit_field_values)
112
+
113
+ deserialized_data = try_deserialize_channel_data(raw_values)
114
+
115
+ assert len(deserialized_data) == 2
116
+
117
+ metadata_12v, time_series_12v = deserialized_data[0]
118
+ metadata_heater, time_series_heater = deserialized_data[1]
119
+
120
+ assert metadata_12v.data_type == CHANNEL_DATA_TYPE_BIT_FIELD
121
+ assert metadata_heater.data_type == CHANNEL_DATA_TYPE_BIT_FIELD
122
+
123
+ assert metadata_12v.channel.name == "gpio.12v"
124
+ assert metadata_heater.channel.name == "gpio.heater"
125
+
126
+ assert len(time_series_12v.time_column) == 1
127
+ assert len(time_series_12v.value_column) == 1
128
+ assert len(time_series_heater.time_column) == 1
129
+ assert len(time_series_heater.value_column) == 1
130
+
131
+ assert time_series_12v.value_column[0] == value_a
132
+ assert time_series_heater.value_column[0] == value_b
133
+ assert time_series_12v.time_column[0] == to_timestamp_nanos(time_a)
134
+ assert time_series_heater.time_column[0] == to_timestamp_nanos(time_a)
@@ -0,0 +1,276 @@
1
+ from contextlib import contextmanager
2
+ from datetime import datetime, timedelta, timezone
3
+ from typing import Dict, Iterator
4
+
5
+ import pytest
6
+ from google.protobuf.any_pb2 import Any
7
+ from pytest_mock import MockFixture, MockType
8
+ from sift.assets.v1.assets_pb2 import Asset
9
+ from sift.channels.v2.channels_pb2 import Channel
10
+ from sift.common.type.v1.channel_bit_field_element_pb2 import ChannelBitFieldElement
11
+ from sift.common.type.v1.channel_data_type_pb2 import (
12
+ CHANNEL_DATA_TYPE_BIT_FIELD,
13
+ CHANNEL_DATA_TYPE_DOUBLE,
14
+ )
15
+ from sift.data.v1.data_pb2 import (
16
+ BitFieldElementValues,
17
+ BitFieldValue,
18
+ BitFieldValues,
19
+ DoubleValue,
20
+ DoubleValues,
21
+ Metadata,
22
+ )
23
+ from sift.runs.v2.runs_pb2 import Run
24
+
25
+ from sift_py._internal.test_util.channel import MockAsyncChannel
26
+ from sift_py._internal.time import to_timestamp_pb
27
+ from sift_py.data.query import ChannelQuery, DataQuery
28
+ from sift_py.data.service import DataService
29
+
30
+
31
+ @pytest.mark.asyncio
32
+ async def test_data_service_execute_regular_channels(mocker: MockFixture):
33
+ with patch_grpc_calls_channels(mocker) as mocks:
34
+ channel = MockAsyncChannel()
35
+ data_service = DataService(channel)
36
+
37
+ start_time = datetime.now(timezone.utc)
38
+ end_time = start_time + timedelta(minutes=2)
39
+
40
+ query = DataQuery(
41
+ asset_name="NostromoLV428",
42
+ start_time=start_time,
43
+ end_time=end_time,
44
+ sample_ms=0,
45
+ channels=[
46
+ ChannelQuery(
47
+ channel_name="velocity",
48
+ component="mainmotor",
49
+ run_name="[NostromoLV426].1720141748.047512",
50
+ ),
51
+ ChannelQuery(
52
+ channel_name="gpio",
53
+ run_name="[NostromoLV426].1720141748.047512",
54
+ ),
55
+ ChannelQuery(
56
+ channel_name="valve.pressure",
57
+ run_name="[NostromoLV426].1720141748.047512",
58
+ ),
59
+ ],
60
+ )
61
+
62
+ result = await data_service.execute(query)
63
+
64
+ mock_get_asset = mocks["mock_get_asset_by_name"]
65
+ mock_get_channels = mocks["mock_get_channels_by_asset_id"]
66
+ mock_get_runs = mocks["mock_get_runs_by_names"]
67
+
68
+ mock_get_asset.assert_called_once()
69
+ mock_get_channels.assert_called_once()
70
+ mock_get_runs.assert_called_once()
71
+
72
+ # bit field elements count as separate channels
73
+ assert len(result.all_channels()) == 4
74
+ assert not result.channel("velocity")
75
+ assert not result.channels("velocity")
76
+ assert len(result.channels("mainmotor.velocity")) == 1
77
+
78
+ velocity = result.channel("mainmotor.velocity")
79
+ assert velocity is not None
80
+ assert len(velocity.timestamps) == 2
81
+ assert len(velocity.time_column()["time"]) == 2
82
+ assert len(velocity.time_column("custom_column_name")["custom_column_name"]) == 2
83
+ assert len(velocity.value_column()["mainmotor.velocity"]) == 2
84
+ assert len(velocity.value_column("custom_column_name")["custom_column_name"]) == 2
85
+
86
+ all_columns = velocity.columns()
87
+ assert len(all_columns) == 2
88
+ assert len(all_columns["time"]) == 2
89
+ assert len(all_columns["mainmotor.velocity"]) == 2
90
+
91
+ all_columns_custom = velocity.columns(
92
+ time_column_name="ts",
93
+ value_column_name="velocity",
94
+ )
95
+ assert len(all_columns_custom) == 2
96
+ assert len(all_columns_custom["ts"]) == 2
97
+ assert len(all_columns_custom["velocity"]) == 2
98
+
99
+ gpio = result.channel("gpio")
100
+ assert not gpio
101
+
102
+ gpio_12v = result.channel("gpio.12v")
103
+ assert gpio_12v is not None
104
+ assert len(gpio_12v.timestamps) == 1
105
+ assert len(gpio_12v.time_column()["time"]) == 1
106
+ assert len(gpio_12v.time_column("custom_column_name")["custom_column_name"]) == 1
107
+ assert len(gpio_12v.value_column()["gpio.12v"]) == 1
108
+ assert len(gpio_12v.value_column("12v")["12v"]) == 1
109
+
110
+ gpio_heater = result.channel("gpio.heater")
111
+ assert gpio_heater is not None
112
+ assert len(gpio_heater.timestamps) == 1
113
+ assert len(gpio_heater.time_column()["time"]) == 1
114
+ assert len(gpio_heater.time_column("custom_column_name")["custom_column_name"]) == 1
115
+ assert len(gpio_heater.value_column()["gpio.heater"]) == 1
116
+ assert len(gpio_heater.value_column("heater")["heater"]) == 1
117
+
118
+ pressure = result.channel("valve.pressure")
119
+ assert pressure is not None
120
+ assert len(pressure.timestamps) == 2
121
+ assert len(pressure.time_column()["time"]) == 2
122
+ assert len(pressure.time_column("custom_column_name")["custom_column_name"]) == 2
123
+ assert len(pressure.value_column()["valve.pressure"]) == 2
124
+ assert len(pressure.value_column("custom_column_name")["custom_column_name"]) == 2
125
+
126
+ all_columns = pressure.columns()
127
+ assert len(all_columns) == 2
128
+ assert len(all_columns["time"]) == 2
129
+ assert len(all_columns["valve.pressure"]) == 2
130
+
131
+ all_columns_custom = pressure.columns(
132
+ time_column_name="ts",
133
+ value_column_name="valve.pressure",
134
+ )
135
+ assert len(all_columns_custom) == 2
136
+ assert len(all_columns_custom["ts"]) == 2
137
+ assert len(all_columns_custom["valve.pressure"]) == 2
138
+
139
+
140
+ @contextmanager
141
+ def patch_grpc_calls_channels(mocker: MockFixture) -> Iterator[Dict[str, MockType]]:
142
+ mock__get_asset_by_name = mocker.patch.object(DataService, "_get_asset_by_name")
143
+ mock__get_asset_by_name.return_value = Asset(
144
+ asset_id="b7955799-9893-4acf-bf14-50052284020c", name="NostromoLV428"
145
+ )
146
+
147
+ mock__get_channels_by_asset_id = mocker.patch.object(DataService, "_get_channels_by_asset_id")
148
+ mock__get_channels_by_asset_id.return_value = [
149
+ Channel(
150
+ channel_id="e8662647-12f7-465f-85dc-cb02513944e0",
151
+ name="velocity",
152
+ component="mainmotor",
153
+ data_type=CHANNEL_DATA_TYPE_DOUBLE,
154
+ ),
155
+ Channel(
156
+ channel_id="97e25141-ed3e-4538-b063-c3eac30838ce",
157
+ name="gpio",
158
+ data_type=CHANNEL_DATA_TYPE_BIT_FIELD,
159
+ ),
160
+ Channel(
161
+ channel_id="87e25141-ed3e-4538-b063-c3eac30838cd",
162
+ name="valve.pressure",
163
+ data_type=CHANNEL_DATA_TYPE_DOUBLE,
164
+ ),
165
+ ]
166
+
167
+ mock__get_runs_by_names = mocker.patch.object(DataService, "_get_runs_by_names")
168
+ mock__get_runs_by_names.return_value = [
169
+ Run(
170
+ run_id="9b7f6c5f-cabc-4481-b048-6f12fc6b5b68",
171
+ name="[NostromoLV426].1720141748.047512",
172
+ )
173
+ ]
174
+
175
+ time_a = "2024-07-04T18:09:08.555-07:00"
176
+ time_b = "2024-07-04T18:09:09.555-07:00"
177
+
178
+ velocity_values = DoubleValues(
179
+ metadata=Metadata(
180
+ data_type=CHANNEL_DATA_TYPE_DOUBLE,
181
+ channel=Metadata.Channel(name="velocity", component="mainmotor"),
182
+ ),
183
+ values=[
184
+ DoubleValue(
185
+ timestamp=to_timestamp_pb(time_a),
186
+ value=10,
187
+ ),
188
+ DoubleValue(
189
+ timestamp=to_timestamp_pb(time_b),
190
+ value=11,
191
+ ),
192
+ ],
193
+ )
194
+
195
+ raw_velocity_values = Any()
196
+ raw_velocity_values.Pack(velocity_values)
197
+
198
+ time_a = "2024-07-04T18:09:08.555-07:00"
199
+ time_b = "2024-07-04T18:09:09.555-07:00"
200
+
201
+ pressure_values = DoubleValues(
202
+ metadata=Metadata(
203
+ data_type=CHANNEL_DATA_TYPE_DOUBLE,
204
+ channel=Metadata.Channel(name="valve.pressure"),
205
+ ),
206
+ values=[
207
+ DoubleValue(
208
+ timestamp=to_timestamp_pb(time_a),
209
+ value=10,
210
+ ),
211
+ DoubleValue(
212
+ timestamp=to_timestamp_pb(time_b),
213
+ value=11,
214
+ ),
215
+ ],
216
+ )
217
+
218
+ raw_pressure_values = Any()
219
+ raw_pressure_values.Pack(pressure_values)
220
+
221
+ bit_field_values = BitFieldValues(
222
+ metadata=Metadata(
223
+ data_type=CHANNEL_DATA_TYPE_BIT_FIELD,
224
+ channel=Metadata.Channel(
225
+ name="gpio",
226
+ bit_field_elements=[
227
+ ChannelBitFieldElement(
228
+ name="12v",
229
+ index=0,
230
+ bit_count=4,
231
+ ),
232
+ ChannelBitFieldElement(
233
+ name="heater",
234
+ index=4,
235
+ bit_count=4,
236
+ ),
237
+ ],
238
+ ),
239
+ ),
240
+ values=[
241
+ BitFieldElementValues(
242
+ name="12v",
243
+ values=[
244
+ BitFieldValue(
245
+ timestamp=to_timestamp_pb(time_a),
246
+ value=int("10000001", 2),
247
+ )
248
+ ],
249
+ ),
250
+ BitFieldElementValues(
251
+ name="heater",
252
+ values=[
253
+ BitFieldValue(
254
+ timestamp=to_timestamp_pb(time_a),
255
+ value=int("11110001", 2),
256
+ )
257
+ ],
258
+ ),
259
+ ],
260
+ )
261
+
262
+ raw_bit_field_values = Any()
263
+ raw_bit_field_values.Pack(bit_field_values)
264
+
265
+ mock__get_data = mocker.patch.object(DataService, "_get_data")
266
+ mock__get_data.side_effect = [
267
+ [[raw_velocity_values]],
268
+ [[raw_bit_field_values]],
269
+ [[raw_pressure_values]],
270
+ ]
271
+ yield {
272
+ "mock_get_asset_by_name": mock__get_asset_by_name,
273
+ "mock_get_runs_by_names": mock__get_runs_by_names,
274
+ "mock_get_channels_by_asset_id": mock__get_channels_by_asset_id,
275
+ "mock_get_data": mock__get_data,
276
+ }
@@ -0,0 +1,10 @@
1
+ import re
2
+
3
+ CHANNEL_REFERENCE_REGEX = re.compile(r"^\$\d+$")
4
+
5
+
6
+ def validate_channel_reference(ref: str):
7
+ if CHANNEL_REFERENCE_REGEX.match(ref) is None:
8
+ raise ValueError(
9
+ f"Invalid channel reference key '{ref}'. Expected an integer prefixed with '$' e.g. '$1', '$2', and so on."
10
+ )
sift_py/data/error.py ADDED
@@ -0,0 +1,5 @@
1
+ class DataError(Exception):
2
+ msg: str
3
+
4
+ def __init__(self, msg: str):
5
+ super().__init__(msg)