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,5 @@
1
+ """
2
+ INTERNAL MODULE
3
+
4
+ Everything inside of this module is not meant to be used publicly.
5
+ """
@@ -0,0 +1,18 @@
1
+ """
2
+ Utilities to interact with APIs that have a CEL-based interface.
3
+ """
4
+
5
+ from typing import Iterable
6
+
7
+
8
+ def cel_in(field: str, values: Iterable[str]) -> str:
9
+ """
10
+ Produces a list membership CEL expression. Example:
11
+
12
+ ```python
13
+ > print(cel_in("name", ["foo", "bar"]))
14
+ name in ["foo", "bar"]
15
+ ```
16
+ """
17
+ items = ",".join([f'"{val}"' for val in values])
18
+ return f"{field} in [{items}]"
@@ -0,0 +1,42 @@
1
+ from typing import List, Optional, cast
2
+
3
+ from sift.channels.v2.channels_pb2 import Channel as ChannelPb
4
+ from sift.channels.v2.channels_pb2 import ListChannelsRequest, ListChannelsResponse
5
+ from sift.channels.v2.channels_pb2_grpc import ChannelServiceStub
6
+
7
+
8
+ def channel_fqn(name: str, component: Optional[str]) -> str:
9
+ return name if component is None or len(component) == 0 else f"{component}.{name}"
10
+
11
+
12
+ def get_channels(
13
+ channel_service: ChannelServiceStub,
14
+ filter: str,
15
+ page_size: int = 1_000,
16
+ page_token: str = "",
17
+ ) -> List[ChannelPb]:
18
+ """
19
+ Queries all channels with the given filter. Filter must be a CEL expression.
20
+ """
21
+ channels_pb: List[ChannelPb] = []
22
+
23
+ req = ListChannelsRequest(
24
+ filter=filter,
25
+ page_size=page_size,
26
+ page_token=page_token,
27
+ )
28
+ res = cast(ListChannelsResponse, channel_service.ListChannels(req))
29
+ channels_pb.extend(res.channels)
30
+ next_page_token = res.next_page_token
31
+
32
+ while len(next_page_token) > 0:
33
+ req = ListChannelsRequest(
34
+ filter=filter,
35
+ page_size=page_size,
36
+ page_token=page_token,
37
+ )
38
+ res = cast(ListChannelsResponse, channel_service.ListChannels(req))
39
+ channels_pb.extend(res.channels)
40
+ next_page_token = res.next_page_token
41
+
42
+ return channels_pb
@@ -0,0 +1,3 @@
1
+ """
2
+ Utilities for type-conversion and type-casting.
3
+ """
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from abc import ABC, abstractmethod
5
+ from typing import Any
6
+
7
+
8
+ class AsJson(ABC):
9
+ """
10
+ Utility sub-types that require custom-serialization meant to be used in conjunction with the
11
+ `to_json` function. Sub-types should implement `as_json` which should return the object that
12
+ you want passed to `json.dumps`.
13
+ """
14
+
15
+ @abstractmethod
16
+ def as_json(self) -> Any:
17
+ pass
18
+
19
+
20
+ def to_json(value: Any) -> str:
21
+ """
22
+ Serializes `value` to a JSON string uses the `AsJson.as_json` implementation of the type.
23
+ """
24
+ return json.dumps(value, default=lambda x: x.as_json())
@@ -0,0 +1,34 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Generic, Type, TypeVar
5
+
6
+ from google.protobuf.message import Message
7
+
8
+ ProtobufMessage = Message
9
+
10
+ T = TypeVar("T", bound=ProtobufMessage)
11
+
12
+
13
+ class AsProtobuf(ABC, Generic[T]):
14
+ """
15
+ Abstract base class used to create create sub-types that can be treated
16
+ as an object that can be converted into an instance of `ProtobufMessage`.
17
+
18
+ If there are multiple possible protobuf targets then `as_pb` may be overloaded.
19
+ """
20
+
21
+ @abstractmethod
22
+ def as_pb(self, klass: Type[T]) -> T:
23
+ """
24
+ Performs the conversion into a sub-type of `ProtobufMessage`.
25
+ """
26
+ pass
27
+
28
+ @classmethod
29
+ @abstractmethod
30
+ def from_pb(cls, message: T) -> T:
31
+ """
32
+ Converts a protobuf object to the type of the sub-class class.
33
+ """
34
+ pass
@@ -0,0 +1,9 @@
1
+ from datetime import datetime
2
+
3
+ from google.protobuf.timestamp_pb2 import Timestamp
4
+
5
+
6
+ def to_pb_timestamp(timestamp: datetime) -> Timestamp:
7
+ timestamp_pb = Timestamp()
8
+ timestamp_pb.FromDatetime(timestamp)
9
+ return timestamp_pb
File without changes
@@ -0,0 +1,136 @@
1
+ from collections.abc import AsyncIterable, Iterable
2
+ from typing import Any, Callable, Optional, Union
3
+
4
+ import grpc
5
+ import grpc.aio as grpc_aio
6
+ from grpc.aio import Channel as AsyncChannel
7
+ from grpc_testing import Channel
8
+
9
+ SerializingFunction = Callable[[Any], bytes]
10
+ DeserializingFunction = Callable[[bytes], Any]
11
+ DoneCallbackType = Callable[[Any], None]
12
+ RequestIterableType = Union[Iterable, AsyncIterable]
13
+ ResponseIterableType = AsyncIterable
14
+
15
+
16
+ class MockChannel(Channel):
17
+ """
18
+ Used as a mock gRPC channel
19
+ """
20
+
21
+ def take_unary_unary(self, method_descriptor):
22
+ pass
23
+
24
+ def take_unary_stream(self, method_descriptor):
25
+ pass
26
+
27
+ def take_stream_unary(self, method_descriptor):
28
+ pass
29
+
30
+ def take_stream_stream(self, method_descriptor):
31
+ pass
32
+
33
+ def subscribe(self, callback, try_to_connect=False):
34
+ pass
35
+
36
+ def unsubscribe(self, callback):
37
+ pass
38
+
39
+ def unary_unary(
40
+ self,
41
+ method,
42
+ request_serializer=None,
43
+ response_deserializer=None,
44
+ _registered_method=False,
45
+ ):
46
+ pass
47
+
48
+ def unary_stream(
49
+ self,
50
+ method,
51
+ request_serializer=None,
52
+ response_deserializer=None,
53
+ _registered_method=False,
54
+ ):
55
+ pass
56
+
57
+ def stream_unary(
58
+ self,
59
+ method,
60
+ request_serializer=None,
61
+ response_deserializer=None,
62
+ _registered_method=False,
63
+ ):
64
+ pass
65
+
66
+ def stream_stream(
67
+ self,
68
+ method,
69
+ request_serializer=None,
70
+ response_deserializer=None,
71
+ _registered_method=False,
72
+ ):
73
+ pass
74
+
75
+ def close(self):
76
+ pass
77
+
78
+ def __enter__(self):
79
+ pass
80
+
81
+ def __exit__(self, exc_type, exc_val, exc_tb):
82
+ pass
83
+
84
+
85
+ class MockAsyncChannel(AsyncChannel):
86
+ async def __aenter__(self):
87
+ pass
88
+
89
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
90
+ pass
91
+
92
+ async def close(self, grace: Optional[float] = None):
93
+ pass
94
+
95
+ def get_state(self, try_to_connect: bool = False) -> grpc.ChannelConnectivity: ...
96
+
97
+ async def wait_for_state_change(
98
+ self,
99
+ last_observed_state: grpc.ChannelConnectivity,
100
+ ) -> None:
101
+ return None
102
+
103
+ async def channel_ready(self) -> None:
104
+ return None
105
+
106
+ def unary_unary(
107
+ self,
108
+ method: str,
109
+ request_serializer: Optional[SerializingFunction] = None,
110
+ response_deserializer: Optional[DeserializingFunction] = None,
111
+ _registered_method: Optional[bool] = False,
112
+ ) -> grpc_aio.UnaryUnaryMultiCallable: ...
113
+
114
+ def unary_stream(
115
+ self,
116
+ method: str,
117
+ request_serializer: Optional[SerializingFunction] = None,
118
+ response_deserializer: Optional[DeserializingFunction] = None,
119
+ _registered_method: Optional[bool] = False,
120
+ ) -> grpc_aio.UnaryStreamMultiCallable: ...
121
+
122
+ def stream_unary(
123
+ self,
124
+ method: str,
125
+ request_serializer: Optional[SerializingFunction] = None,
126
+ response_deserializer: Optional[DeserializingFunction] = None,
127
+ _registered_method: Optional[bool] = False,
128
+ ) -> grpc_aio.StreamUnaryMultiCallable: ...
129
+
130
+ def stream_stream(
131
+ self,
132
+ method: str,
133
+ request_serializer: Optional[SerializingFunction] = None,
134
+ response_deserializer: Optional[DeserializingFunction] = None,
135
+ _registered_method: Optional[bool] = False,
136
+ ) -> grpc_aio.StreamStreamMultiCallable: ...
@@ -0,0 +1,14 @@
1
+ from types import ModuleType
2
+ from typing import Callable
3
+
4
+
5
+ def _mock_path(subject_module: ModuleType) -> Callable[[Callable], str]:
6
+ """
7
+ Returns a function that can be used to conveniently generate the mock path
8
+ for a function which could then be passed to `pytest_mock.MockFixture.patch`
9
+ """
10
+
11
+ def mock_fn(fn: Callable) -> str:
12
+ return f"{subject_module.__name__}.{fn.__name__}"
13
+
14
+ return mock_fn
@@ -0,0 +1,62 @@
1
+ import abc
2
+ from typing import Any, Callable, Optional, Tuple, cast
3
+
4
+ import grpc
5
+
6
+
7
+ class ServerInterceptor(grpc.ServerInterceptor, metaclass=abc.ABCMeta):
8
+ @abc.abstractmethod
9
+ def intercept(
10
+ self,
11
+ method: Callable,
12
+ request_or_iterator: Any,
13
+ context: grpc.ServicerContext,
14
+ method_name: str,
15
+ ) -> Any:
16
+ return method(request_or_iterator, context)
17
+
18
+ def intercept_service(self, continuation, handler_call_details):
19
+ next_handler = continuation(handler_call_details)
20
+ if next_handler is None:
21
+ return
22
+
23
+ handler_factory, next_handler_method = _get_factory_and_method(next_handler)
24
+
25
+ def invoke_intercept_method(request_or_iterator, context):
26
+ method_name = handler_call_details.method
27
+ return self.intercept(
28
+ next_handler_method,
29
+ request_or_iterator,
30
+ context,
31
+ method_name,
32
+ )
33
+
34
+ return handler_factory(
35
+ invoke_intercept_method,
36
+ request_deserializer=next_handler.request_deserializer,
37
+ response_serializer=next_handler.response_serializer,
38
+ )
39
+
40
+
41
+ class _RpcHandler(grpc.RpcMethodHandler):
42
+ unary_unary: Optional[Callable]
43
+ unary_stream: Optional[Callable]
44
+ stream_unary: Optional[Callable]
45
+ stream_stream: Optional[Callable]
46
+
47
+
48
+ def _get_factory_and_method(
49
+ rpc_handler: grpc.RpcMethodHandler,
50
+ ) -> Tuple[Callable, Callable]:
51
+ handler = cast(_RpcHandler, rpc_handler)
52
+
53
+ if handler.unary_unary:
54
+ return grpc.unary_unary_rpc_method_handler, handler.unary_unary
55
+ elif handler.unary_stream:
56
+ return grpc.unary_stream_rpc_method_handler, handler.unary_stream
57
+ elif handler.stream_unary:
58
+ return grpc.stream_unary_rpc_method_handler, handler.stream_unary
59
+ elif handler.stream_stream:
60
+ return grpc.stream_stream_rpc_method_handler, handler.stream_stream
61
+ else:
62
+ raise Exception("Unreachable")
@@ -0,0 +1,48 @@
1
+ from datetime import datetime, timezone
2
+ from typing import Union, cast
3
+
4
+ import pandas as pd
5
+ from google.protobuf.timestamp_pb2 import Timestamp as TimestampPb
6
+
7
+
8
+ def to_timestamp_nanos(arg: Union[TimestampPb, pd.Timestamp, datetime, str, int]) -> pd.Timestamp:
9
+ """
10
+ Converts a variety of time-types to a pandas timestamp which supports nano-second precision.
11
+ """
12
+
13
+ if isinstance(arg, pd.Timestamp):
14
+ return arg
15
+ elif isinstance(arg, TimestampPb):
16
+ seconds = arg.seconds
17
+ nanos = arg.nanos
18
+
19
+ dt = datetime.fromtimestamp(seconds, tz=timezone.utc)
20
+ ts = pd.Timestamp(dt)
21
+
22
+ return cast(pd.Timestamp, ts + pd.Timedelta(nanos, unit="ns"))
23
+
24
+ elif isinstance(arg, int):
25
+ dt = datetime.fromtimestamp(arg, tz=timezone.utc)
26
+ return cast(pd.Timestamp, pd.Timestamp(dt))
27
+
28
+ else:
29
+ return cast(pd.Timestamp, pd.Timestamp(arg))
30
+
31
+
32
+ def to_timestamp_pb(arg: Union[datetime, str, int]) -> TimestampPb:
33
+ """
34
+ Mainly used for testing at the moment. If using this for non-testing purposes
35
+ should probably make this more robust and support nano-second precision.
36
+ """
37
+
38
+ ts = TimestampPb()
39
+
40
+ if isinstance(arg, datetime):
41
+ ts.FromDatetime(arg)
42
+ return ts
43
+ elif isinstance(arg, int):
44
+ ts.FromDatetime(datetime.fromtimestamp(arg))
45
+ return ts
46
+ else:
47
+ ts.FromDatetime(datetime.fromisoformat(arg))
48
+ return ts
@@ -0,0 +1,39 @@
1
+ from typing import List, cast
2
+
3
+ from sift.common.type.v1.user_pb2 import User
4
+ from sift.users.v2.users_pb2 import ListActiveUsersRequest, ListActiveUsersResponse
5
+ from sift.users.v2.users_pb2_grpc import UserServiceStub
6
+
7
+
8
+ def get_active_users(
9
+ user_service: UserServiceStub,
10
+ filter: str,
11
+ page_size: int = 1_000,
12
+ page_token: str = "",
13
+ ) -> List[User]:
14
+ """
15
+ Get active users from the user service with the given filter.
16
+ The filter must be a CEL expression.
17
+ """
18
+ users_pb: List[User] = []
19
+
20
+ req = ListActiveUsersRequest(
21
+ filter=filter,
22
+ page_size=page_size,
23
+ page_token=page_token,
24
+ )
25
+ res = cast(ListActiveUsersResponse, user_service.ListActiveUsers(req))
26
+ users_pb.extend(res.users)
27
+ next_page_token = res.next_page_token
28
+
29
+ while len(next_page_token) > 0:
30
+ req = ListActiveUsersRequest(
31
+ filter=filter,
32
+ page_size=page_size,
33
+ page_token=page_token,
34
+ )
35
+ res = cast(ListActiveUsersResponse, user_service.ListActiveUsers(req))
36
+ users_pb.extend(res.users)
37
+ next_page_token = res.next_page_token
38
+
39
+ return users_pb
@@ -0,0 +1,171 @@
1
+ """
2
+ This module contains tools to download telemetry from the Sift data API. The
3
+ core component of this module is the `sift_py.data.service.DataService` and the
4
+ `sift_py.data.query` module. The former is what's used to execute a data query,
5
+ while the latter is what's used to actually construct the query. A typical query could look
6
+ something like this:
7
+
8
+ ```python
9
+ query = DataQuery(
10
+ asset_name="NostromoLV426",
11
+ start_time="2024-07-04T18:09:08.555-07:00",
12
+ end_time="2024-07-04T18:09:11.556-07:00",
13
+ sample_ms=16,
14
+ channels=[
15
+ ChannelQuery(
16
+ channel_name="voltage",
17
+ run_name="[NostromoLV426].1720141748.047512"
18
+ ),
19
+ ChannelQuery(
20
+ channel_name="velocity",
21
+ component="mainmotors",
22
+ run_name="[NostromoLV426].1720141748.047512",
23
+ ),
24
+ ChannelQuery(
25
+ channel_name="gpio",
26
+ run_name="[NostromoLV426].1720141748.047512",
27
+ ),
28
+ ],
29
+ )
30
+ ```
31
+
32
+ This query, once passed to the `sift_py.data.service.DataService.execute` method, will
33
+ fetch data between `start_time` and `end_time` at the sampling rate given by `sample_ms`.
34
+
35
+ > ⚠️ **Warning**: Note on Performance
36
+ >
37
+ > Currently the results of a query are all buffered in memory, so it it best to be mindful
38
+ > about your memory limitations and overall performance requirements when requesting data
39
+ > within a large time range and a slow sampling rate. Full-fidelity data is returned
40
+ > when the `sample_ms` is set to `0`.
41
+
42
+ The data API allows you to download telemetry for both channels as well as calculated
43
+ channels. The following examples demonstrate how to download data for both channels and
44
+ calculated channels, respectively.
45
+
46
+ * [Regular Channels](#regular-channels)
47
+ * [Calculated Channels](#calculated-channels)
48
+
49
+ ## Regular Channels
50
+
51
+ ```python
52
+ import asyncio
53
+ import functools
54
+ import pandas as pd
55
+ from sift_py.data.query import ChannelQuery, DataQuery
56
+ from sift_py.grpc.transport import SiftChannelConfig, use_sift_async_channel
57
+ from sift_py.data.service import DataService
58
+
59
+
60
+ async def channel_demo():
61
+ channel_config: SiftChannelConfig = {
62
+ "apikey": "my-key"
63
+ "uri": "sift-uri"
64
+ }
65
+
66
+ async with use_sift_async_channel(channel_config) as channel:
67
+ data_service = DataService(channel)
68
+
69
+ query = DataQuery(
70
+ asset_name="NostromoLV426",
71
+ start_time="2024-07-04T18:09:08.555-07:00",
72
+ end_time="2024-07-04T18:09:11.556-07:00",
73
+ channels=[
74
+ ChannelQuery(
75
+ channel_name="voltage",
76
+ run_name="[NostromoLV426].1720141748.047512"
77
+ ),
78
+ ChannelQuery(
79
+ channel_name="velocity",
80
+ component="mainmotors",
81
+ run_name="[NostromoLV426].1720141748.047512",
82
+ ),
83
+ ChannelQuery(
84
+ channel_name="gpio",
85
+ run_name="[NostromoLV426].1720141748.047512",
86
+ ),
87
+ ],
88
+ )
89
+
90
+ result = await data_service.execute(query)
91
+
92
+ data_frames = [
93
+ pd.DataFrame(data.columns())
94
+ for data in result.channels("voltage", "mainmotors.velocity", "gpio.12v")
95
+ ]
96
+
97
+ merged_frame = functools.reduce(
98
+ lambda x, y: pd.merge_asof(x, y, on="time"), data_frames
99
+ )
100
+
101
+ merged_frame.to_csv("my_csv.csv")
102
+
103
+ if __name__ == "__main__":
104
+ asyncio.run(example())
105
+ ```
106
+
107
+ ## Calculated Channels
108
+
109
+ ```python
110
+ import asyncio
111
+ import functools
112
+ import pandas as pd
113
+ from sift_py.data.query import ChannelQuery, DataQuery
114
+ from sift_py.grpc.transport import SiftChannelConfig, use_sift_async_channel
115
+ from sift_py.data.service import DataService
116
+
117
+
118
+ async def channel_demo():
119
+ channel_config: SiftChannelConfig = {
120
+ "apikey": "my-key"
121
+ "uri": "sift-uri"
122
+ }
123
+
124
+ async with use_sift_async_channel(channel_config) as channel:
125
+ data_service = DataService(channel)
126
+
127
+ query = DataQuery(
128
+ asset_name="NostromoLV426",
129
+ start_time="2024-07-04T18:09:08.555-07:00",
130
+ end_time="2024-07-04T18:09:11.556-07:00",
131
+ channels=[
132
+ CalculatedChannelQuery(
133
+ channel_key="calc-voltage",
134
+ expression="$1 + 10",
135
+ expression_channel_references=[
136
+ {
137
+ "reference": "$1",
138
+ "channel_name": "voltage",
139
+ },
140
+ ],
141
+ run_name="[NostromoLV426].1720141748.047512",
142
+ ),
143
+ CalculatedChannelQuery(
144
+ channel_key="calc-velocity",
145
+ expression="$1 * 2",
146
+ expression_channel_references=[
147
+ {
148
+ "reference": "$1",
149
+ "channel_name": "velocity",
150
+ "component": "mainmotors",
151
+ },
152
+ ],
153
+ run_name="[NostromoLV426].1720141748.047512",
154
+ ),
155
+ ],
156
+ )
157
+
158
+ result = await data_service.execute(query)
159
+ calc_voltage, calc_velocity = result.channels("calc-voltage", "calc-velocity")
160
+
161
+ calc_voltage_df = pd.DataFrame(calc_voltage.columns())
162
+ calc_velocity_df = pd.DataFrame(calc_velocity.columns())
163
+
164
+ merged_frame = pd.merge_asof(calc_voltage_df, calc_velocity_df, on="time")
165
+
166
+ merged_frame.to_csv("my_csv.csv")
167
+
168
+ if __name__ == "__main__":
169
+ asyncio.run(example())
170
+ ```
171
+ """
@@ -0,0 +1,38 @@
1
+ from typing import Any, List
2
+
3
+ import pandas as pd
4
+
5
+ from sift_py.ingestion.channel import ChannelDataType
6
+
7
+
8
+ class ChannelTimeSeries:
9
+ data_type: ChannelDataType
10
+ time_column: List[pd.Timestamp]
11
+ value_column: List[Any]
12
+
13
+ def __init__(
14
+ self,
15
+ data_type: ChannelDataType,
16
+ time_column: List[pd.Timestamp],
17
+ value_column: List[Any],
18
+ ):
19
+ if len(time_column) != len(value_column):
20
+ raise Exception("Both arguments, `time_column` and `value_column` must equal lengths.")
21
+
22
+ self.data_type = data_type
23
+ self.time_column = time_column
24
+ self.value_column = value_column
25
+
26
+ def sort_time_series(self):
27
+ points = [(t, v) for t, v in zip(self.time_column, self.value_column)]
28
+ points.sort(key=lambda x: x[0])
29
+
30
+ time_column = []
31
+ value_column = []
32
+
33
+ for ts, val in points:
34
+ time_column.append(ts)
35
+ value_column.append(val)
36
+
37
+ self.time_column = time_column
38
+ self.value_column = value_column