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
sift_py/yaml/rule.py ADDED
@@ -0,0 +1,321 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from pathlib import Path
5
+ from typing import Any, Dict, List, Literal, Union, cast
6
+
7
+ import yaml
8
+ from typing_extensions import NotRequired, TypedDict
9
+
10
+ from sift_py.ingestion.config.yaml.error import YamlConfigError
11
+ from sift_py.rule.config import RuleActionAnnotationKind
12
+ from sift_py.yaml.channel import (
13
+ ChannelConfigYamlSpec,
14
+ _validate_channel_reference,
15
+ )
16
+ from sift_py.yaml.utils import _handle_subdir, _type_fqn
17
+
18
+ _SUB_EXPRESSION_REGEX = re.compile(r"^\$[a-zA-Z_]+$")
19
+
20
+
21
+ def load_named_expression_modules(paths: List[Path]) -> Dict[str, str]:
22
+ """
23
+ Takes in a list of paths to YAML files which contains named expressions and processes them into a `dict`.
24
+ The key is the name of the expression and the value is the expression itself. For more information on
25
+ named expression modules see `sift_py/yaml/rule.py`.
26
+ """
27
+
28
+ named_expressions = {}
29
+
30
+ for path in paths:
31
+ named_expr_module = _read_named_expression_module_yaml(path)
32
+
33
+ for name, expr in named_expr_module.items():
34
+ if name in named_expressions:
35
+ raise YamlConfigError(
36
+ f"Encountered expressions with identical names being loaded, '{name}'."
37
+ )
38
+ named_expressions[name] = expr
39
+
40
+ return named_expressions
41
+
42
+
43
+ def load_rule_modules(paths: List[Path]) -> List[RuleYamlSpec]:
44
+ """
45
+ Takes in a list of paths which may either be directories or files containing rule module YAML files,
46
+ and processes them into a `list`. For more information on rule modules see
47
+ RulemoduleYamlSpec in `sift_py/yaml/rule.py`.
48
+ """
49
+
50
+ rule_modules: List[RuleYamlSpec] = []
51
+
52
+ def update_rule_modules(rule_module_path: Path):
53
+ rule_module = _read_rule_module_yaml(rule_module_path)
54
+ rule_modules.extend(rule_module)
55
+
56
+ for path in paths:
57
+ if path.is_dir():
58
+ _handle_subdir(path, update_rule_modules)
59
+ elif path.is_file():
60
+ update_rule_modules(path)
61
+
62
+ return rule_modules
63
+
64
+
65
+ def _read_named_expression_module_yaml(path: Path) -> Dict[str, str]:
66
+ with open(path, "r") as f:
67
+ named_expressions = cast(Dict[Any, Any], yaml.safe_load(f.read()))
68
+
69
+ for key, value in named_expressions.items():
70
+ if not isinstance(key, str):
71
+ raise YamlConfigError(
72
+ f"Expected '{key}' to be a string in named expression module '{path}'."
73
+ )
74
+ if not isinstance(value, str):
75
+ raise YamlConfigError(
76
+ f"Expected expression of '{key}' to be a string in named expression module '{path}'."
77
+ )
78
+
79
+ return cast(Dict[str, str], named_expressions)
80
+
81
+
82
+ def _read_rule_module_yaml(path: Path) -> List[RuleYamlSpec]:
83
+ with open(path, "r") as f:
84
+ module_rules = cast(Dict[Any, Any], yaml.safe_load(f.read()))
85
+ rules = module_rules.get("rules")
86
+ if not isinstance(rules, list):
87
+ raise YamlConfigError(
88
+ f"Expected '{rules}' to be a list in rule module yaml: '{path}'"
89
+ f"{_type_fqn(RuleYamlSpec)}"
90
+ )
91
+
92
+ for rule in cast(List[Any], rules):
93
+ _validate_rule(rule)
94
+
95
+ return cast(List[RuleYamlSpec], rules)
96
+
97
+
98
+ def _validate_rule(val: Any):
99
+ rule = cast(Dict[Any, Any], val)
100
+
101
+ name = rule.get("name")
102
+
103
+ if not isinstance(name, str):
104
+ raise YamlConfigError._invalid_property(name, "- name", "str", ["rules"])
105
+
106
+ channel_references = rule.get("channel_references")
107
+
108
+ if channel_references is not None:
109
+ if not isinstance(channel_references, list):
110
+ raise YamlConfigError._invalid_property(
111
+ channel_references,
112
+ "- channel_references",
113
+ f"List[Dict[str, {_type_fqn(ChannelConfigYamlSpec)}]]",
114
+ ["rules"],
115
+ )
116
+
117
+ for channel_reference in cast(List[Any], channel_references):
118
+ _validate_channel_reference(channel_reference)
119
+
120
+ rule_client_key = rule.get("rule_client_key")
121
+ description = rule.get("description")
122
+ expression = rule.get("expression")
123
+ rule_type = rule.get("type")
124
+ assignee = rule.get("assignee")
125
+ tags = rule.get("tags")
126
+ sub_expressions = rule.get("sub_expressions")
127
+ asset_names = rule.get("asset_names")
128
+ tag_names = rule.get("tag_names")
129
+
130
+ if rule_client_key is not None and not isinstance(rule_client_key, str):
131
+ raise YamlConfigError._invalid_property(
132
+ rule_client_key, "- rule_client_key", "str", ["rules"]
133
+ )
134
+
135
+ if description is not None and not isinstance(description, str):
136
+ raise YamlConfigError._invalid_property(description, "- description", "str", ["rules"])
137
+
138
+ if isinstance(expression, dict):
139
+ expression_name = cast(Dict[Any, Any], expression).get("name")
140
+
141
+ if not isinstance(expression_name, str):
142
+ raise YamlConfigError._invalid_property(
143
+ expression_name,
144
+ "name",
145
+ "str",
146
+ ["rules", "- expression"],
147
+ )
148
+
149
+ elif not isinstance(expression, str):
150
+ raise YamlConfigError._invalid_property(
151
+ expression,
152
+ "- expression",
153
+ "<class 'str'> | <class 'dict'>",
154
+ ["rules"],
155
+ )
156
+
157
+ valid_rule_types = [kind.value for kind in RuleActionAnnotationKind]
158
+
159
+ if rule_type not in valid_rule_types:
160
+ raise YamlConfigError._invalid_property(
161
+ rule_type,
162
+ "- type",
163
+ " | ".join(valid_rule_types),
164
+ ["rules"],
165
+ )
166
+
167
+ if assignee is not None and not isinstance(assignee, str):
168
+ raise YamlConfigError._invalid_property(
169
+ assignee,
170
+ "- assignee",
171
+ "str",
172
+ ["rules"],
173
+ )
174
+
175
+ if tags is not None and not isinstance(tags, list):
176
+ raise YamlConfigError._invalid_property(
177
+ tags,
178
+ "- tags",
179
+ "List[str]",
180
+ ["rules"],
181
+ )
182
+
183
+ if sub_expressions is not None:
184
+ if not isinstance(channel_references, list):
185
+ raise YamlConfigError._invalid_property(
186
+ channel_references,
187
+ "- sub_expressions",
188
+ "List[Dict[str, List[Dict[str, str]]]]",
189
+ ["rules"],
190
+ )
191
+
192
+ for sub_expression in cast(List[Any], sub_expressions):
193
+ _validate_sub_expression(sub_expression)
194
+
195
+ if asset_names is not None and not isinstance(asset_names, list):
196
+ raise YamlConfigError._invalid_property(
197
+ asset_names,
198
+ "- asset_names",
199
+ "List[str]",
200
+ ["rules"],
201
+ )
202
+
203
+ if tag_names is not None and not isinstance(tag_names, list):
204
+ raise YamlConfigError._invalid_property(
205
+ tag_names,
206
+ "- tag_names",
207
+ "List[str]",
208
+ ["rules"],
209
+ )
210
+
211
+
212
+ def _validate_sub_expression(val: Any):
213
+ sub_expression = cast(Dict[Any, Any], val)
214
+
215
+ for key in sub_expression.keys():
216
+ if not isinstance(key, str):
217
+ raise YamlConfigError._invalid_property(
218
+ sub_expression,
219
+ "- <str>",
220
+ "Dict[str, Any]",
221
+ ["rules", "- sub_expressions"],
222
+ )
223
+
224
+ if _SUB_EXPRESSION_REGEX.match(key) is None:
225
+ raise YamlConfigError(
226
+ f"Invalid sub-expression key, '{key}'. Characters must be in the character set [a-zA-Z_] and prefixed with a '$'."
227
+ )
228
+
229
+
230
+ class RuleModuleYamlSpec(TypedDict):
231
+ """
232
+ The formal definition of what a rule module looks like in YAML.
233
+
234
+ `rules`: A list of rules that belong to the module.
235
+ """
236
+
237
+ rules: List[RuleYamlSpec]
238
+
239
+
240
+ class RuleYamlSpec(TypedDict):
241
+ """
242
+ The formal definition of what a single rule looks like in YAML.
243
+
244
+ `name`: Name of the rule.
245
+ `rule_client_key`: User-defined string-key that uniquely identifies this rule config.
246
+ `description`: Description of rule.
247
+ `expression`:
248
+ Either an expression-string or a `sift_py.ingestion.config.yaml.spec.NamedExpressionYamlSpec` referencing a named expression.
249
+ `type`: Determines the action to perform if a rule gets evaluated to true.
250
+ `assignee`: If `type` is `review`, determines who to notify. Expects an email.
251
+ `tags`: Tags to associate with the rule.
252
+ `channel_references`: A list of channel references that maps to an actual channel. More below.
253
+ `sub_expressions`: A list of sub-expressions which is a mapping of place-holders to sub-expressions. Only used if using named expressions.
254
+ `asset_names`: A list of asset names that this rule should be applied to. ONLY VALID if defining rules outside of a telemetry config.
255
+ `tag_names`: A list of tag names that this rule should be applied to. ONLY VALID if defining rules outside of a telemetry config.
256
+
257
+ Channel references:
258
+ A channel reference is a string containing a numerical value prefixed with "$". Examples include "$1", "$2", "$11", and so on.
259
+ The channel reference is mapped to an actual channel config. In YAML it would look something like this:
260
+
261
+ ```yaml
262
+ channel_references:
263
+ - $1: *vehicle_state_channel
264
+ - $2: *voltage_channel
265
+ ```
266
+
267
+ Sub-expressions:
268
+ A sub-expression is made up of two components: A reference and the actual sub-expression. The sub-expression reference is
269
+ a string with a "$" prepended to another string comprised of characters in the following character set: `[a-zA-Z0-9_]`.
270
+ This reference should be mapped to the actual sub-expression. For example, say you have kinematic equations in `kinematics.yml`,
271
+ and the equation you're interested in using looks like the following:
272
+
273
+ ```yaml
274
+ kinetic_energy_gt:
275
+ 0.5 * $mass * $1 * $1 > $threshold
276
+ ```
277
+
278
+ To properly use `kinetic_energy_gt` in your rule, it would look like the following:
279
+
280
+ ```yaml
281
+ rules:
282
+ - name: kinetic_energy
283
+ description: Tracks high energy output while in motion
284
+ type: review
285
+ assignee: bob@example.com
286
+ expression:
287
+ name: kinetic_energy_gt
288
+ channel_references:
289
+ - $1: *velocity_channel
290
+ sub_expressions:
291
+ - $mass: 10
292
+ - $threshold: 470
293
+ tags:
294
+ - nostromo
295
+ ```
296
+ """
297
+
298
+ name: str
299
+ rule_client_key: NotRequired[str]
300
+ description: NotRequired[str]
301
+ expression: Union[str, NamedExpressionYamlSpec]
302
+ type: Union[Literal["phase"], Literal["review"]]
303
+ assignee: NotRequired[str]
304
+ tags: NotRequired[List[str]]
305
+ channel_references: NotRequired[List[Dict[str, ChannelConfigYamlSpec]]]
306
+ sub_expressions: NotRequired[List[Dict[str, str]]]
307
+ asset_names: NotRequired[List[str]]
308
+ tag_names: NotRequired[List[str]]
309
+
310
+
311
+ class NamedExpressionYamlSpec(TypedDict):
312
+ """
313
+ A named expression. This class is the formal definition of what a named expression
314
+ should look like in YAML. The value of `name` may contain a mix of channel references
315
+ and channel identifiers.
316
+
317
+ For a formal definition of channel references and channel identifiers see the following:
318
+ `sift_py.ingestion.config.yaml.spec.RuleYamlSpec`.
319
+ """
320
+
321
+ name: str
sift_py/yaml/utils.py ADDED
@@ -0,0 +1,15 @@
1
+ from pathlib import Path
2
+ from typing import Callable, Type
3
+
4
+
5
+ def _handle_subdir(path: Path, file_handler: Callable):
6
+ """The file_handler callable must accept a Path object as its only argument."""
7
+ for file_in_dir in path.iterdir():
8
+ if file_in_dir.is_dir():
9
+ _handle_subdir(file_in_dir, file_handler)
10
+ elif file_in_dir.is_file():
11
+ file_handler(file_in_dir)
12
+
13
+
14
+ def _type_fqn(typ: Type) -> str:
15
+ return f"{typ.__module__}.{typ.__name__}"
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2024 Azimuth Industries Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,109 @@
1
+ Metadata-Version: 2.1
2
+ Name: sift_stack_py
3
+ Version: 0.3.2
4
+ Summary: Python client library for the Sift API
5
+ Maintainer-email: Sift Software Engineers <engineering@siftstack.com>
6
+ License: Copyright (c) 2024 Azimuth Industries Inc.
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11
+
12
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13
+
14
+ Project-URL: Homepage, https://github.com/sift-stack/sift/tree/main/python
15
+ Project-URL: Documentation, https://docs.siftstack.com/sift_py/sift_py.html
16
+ Project-URL: Repository, https://github.com/sift-stack/sift/tree/main/python
17
+ Project-URL: Changelog, https://github.com/sift-stack/sift/tree/main/python/CHANGELOG.md
18
+ Keywords: sift,sift-stack,siftstack,sift_py
19
+ Classifier: License :: OSI Approved :: MIT License
20
+ Classifier: Programming Language :: Python :: 3.8
21
+ Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Requires-Python: >=3.8
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: grpcio ~=1.13
29
+ Requires-Dist: PyYAML ~=6.0
30
+ Requires-Dist: pandas ~=2.0
31
+ Requires-Dist: protobuf >=4.0
32
+ Requires-Dist: pydantic ~=2.0
33
+ Requires-Dist: pydantic-core ~=2.3
34
+ Requires-Dist: requests ~=2.25
35
+ Requires-Dist: requests-toolbelt ~=1.0
36
+ Requires-Dist: pandas-stubs ~=2.0
37
+ Requires-Dist: types-PyYAML ~=6.0
38
+ Requires-Dist: types-protobuf >=4.0
39
+ Requires-Dist: typing-extensions ~=4.6
40
+ Requires-Dist: types-requests ~=2.25
41
+ Provides-Extra: build
42
+ Requires-Dist: pdoc ==14.5.0 ; extra == 'build'
43
+ Requires-Dist: build ==1.2.1 ; extra == 'build'
44
+ Provides-Extra: development
45
+ Requires-Dist: grpcio-testing ~=1.13 ; extra == 'development'
46
+ Requires-Dist: mypy ==1.10.0 ; extra == 'development'
47
+ Requires-Dist: pyright ==1.1.386 ; extra == 'development'
48
+ Requires-Dist: pytest ==8.2.2 ; extra == 'development'
49
+ Requires-Dist: pytest-asyncio ==0.23.7 ; extra == 'development'
50
+ Requires-Dist: pytest-benchmark ==4.0.0 ; extra == 'development'
51
+ Requires-Dist: pytest-mock ==3.14.0 ; extra == 'development'
52
+ Requires-Dist: ruff ; extra == 'development'
53
+ Provides-Extra: openssl
54
+ Requires-Dist: pyOpenSSL <24.0.0 ; extra == 'openssl'
55
+ Requires-Dist: types-pyOpenSSL <24.0.0 ; extra == 'openssl'
56
+ Requires-Dist: cffi ~=1.14 ; extra == 'openssl'
57
+ Provides-Extra: tdms
58
+ Requires-Dist: npTDMS ~=1.9 ; extra == 'tdms'
59
+
60
+ # Sift Python
61
+ [![pypi](https://img.shields.io/pypi/v/sift-stack-py)](https://pypi.org/project/sift-stack-py/)
62
+ [![pypi](https://img.shields.io/pypi/pyversions/sift-stack-py)](https://pypi.org/project/sift-stack-py/)
63
+ [![docs](https://readthedocs.org/projects/pip/badge/)](https://docs.siftstack.com/sift_py/sift_py.html)
64
+
65
+ This library offers a Python API on top of Sift's protocol buffers to ergonomically interface with the Sift gRPC API.
66
+
67
+ ## Installation
68
+
69
+ To install the Sift Python library:
70
+
71
+ ```
72
+ $ pip install sift-stack-py
73
+ ```
74
+
75
+ ## Documentation
76
+
77
+ Documentation can be found at [this link](https://docs.siftstack.com/sift_py/sift_py.html), however, if you need
78
+ to build the documentation for offline use, read on.
79
+
80
+ ### Offline documentation
81
+
82
+ To build the documentation locally:
83
+
84
+ Clone the main `sift` repository:
85
+
86
+ ```
87
+ $ git clone https://github.com/sift-stack/sift.git
88
+ ```
89
+
90
+ Set your working directory to this project sub-directory:
91
+
92
+ ```
93
+ $ cd python
94
+ ```
95
+
96
+ Install [pdoc](https://pypi.org/project/pdoc/):
97
+
98
+ ```
99
+ $ pip install pdoc
100
+ ```
101
+
102
+ Run `pdoc`.
103
+
104
+ ```
105
+ $ pdoc lib/sift_py
106
+ ```
107
+
108
+ You may optionally set the host and port used by the `pdoc` web-server using the `-p` and `-h` options.
109
+ See `pdoc --help` for more info.