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.
- google/__init__.py +1 -0
- google/api/__init__.py +0 -0
- google/api/annotations_pb2.py +27 -0
- google/api/annotations_pb2.pyi +29 -0
- google/api/annotations_pb2_grpc.py +4 -0
- google/api/annotations_pb2_grpc.pyi +30 -0
- google/api/field_behavior_pb2.py +30 -0
- google/api/field_behavior_pb2.pyi +175 -0
- google/api/field_behavior_pb2_grpc.py +4 -0
- google/api/field_behavior_pb2_grpc.pyi +30 -0
- google/api/http_pb2.py +31 -0
- google/api/http_pb2.pyi +433 -0
- google/api/http_pb2_grpc.py +4 -0
- google/api/http_pb2_grpc.pyi +30 -0
- protoc_gen_openapiv2/__init__.py +0 -0
- protoc_gen_openapiv2/options/__init__.py +0 -0
- protoc_gen_openapiv2/options/annotations_pb2.py +27 -0
- protoc_gen_openapiv2/options/annotations_pb2.pyi +48 -0
- protoc_gen_openapiv2/options/annotations_pb2_grpc.py +4 -0
- protoc_gen_openapiv2/options/annotations_pb2_grpc.pyi +17 -0
- protoc_gen_openapiv2/options/openapiv2_pb2.py +132 -0
- protoc_gen_openapiv2/options/openapiv2_pb2.pyi +1533 -0
- protoc_gen_openapiv2/options/openapiv2_pb2_grpc.py +4 -0
- protoc_gen_openapiv2/options/openapiv2_pb2_grpc.pyi +17 -0
- sift/__init__.py +0 -0
- sift/annotation_logs/__init__.py +0 -0
- sift/annotation_logs/v1/__init__.py +0 -0
- sift/annotation_logs/v1/annotation_logs_pb2.py +115 -0
- sift/annotation_logs/v1/annotation_logs_pb2.pyi +370 -0
- sift/annotation_logs/v1/annotation_logs_pb2_grpc.py +135 -0
- sift/annotation_logs/v1/annotation_logs_pb2_grpc.pyi +84 -0
- sift/annotations/__init__.py +0 -0
- sift/annotations/v1/__init__.py +0 -0
- sift/annotations/v1/annotations_pb2.py +180 -0
- sift/annotations/v1/annotations_pb2.pyi +539 -0
- sift/annotations/v1/annotations_pb2_grpc.py +237 -0
- sift/annotations/v1/annotations_pb2_grpc.pyi +144 -0
- sift/assets/__init__.py +0 -0
- sift/assets/v1/__init__.py +0 -0
- sift/assets/v1/assets_pb2.py +90 -0
- sift/assets/v1/assets_pb2.pyi +235 -0
- sift/assets/v1/assets_pb2_grpc.py +168 -0
- sift/assets/v1/assets_pb2_grpc.pyi +101 -0
- sift/calculated_channels/__init__.py +0 -0
- sift/calculated_channels/v1/__init__.py +0 -0
- sift/calculated_channels/v1/calculated_channels_pb2.py +99 -0
- sift/calculated_channels/v1/calculated_channels_pb2.pyi +280 -0
- sift/calculated_channels/v1/calculated_channels_pb2_grpc.py +101 -0
- sift/calculated_channels/v1/calculated_channels_pb2_grpc.pyi +64 -0
- sift/campaigns/__init__.py +0 -0
- sift/campaigns/v1/__init__.py +0 -0
- sift/campaigns/v1/campaigns_pb2.py +144 -0
- sift/campaigns/v1/campaigns_pb2.pyi +383 -0
- sift/campaigns/v1/campaigns_pb2_grpc.py +169 -0
- sift/campaigns/v1/campaigns_pb2_grpc.pyi +104 -0
- sift/channel_schemas/__init__.py +0 -0
- sift/channel_schemas/v1/__init__.py +0 -0
- sift/channel_schemas/v1/channel_schemas_pb2.py +69 -0
- sift/channel_schemas/v1/channel_schemas_pb2.pyi +117 -0
- sift/channel_schemas/v1/channel_schemas_pb2_grpc.py +101 -0
- sift/channel_schemas/v1/channel_schemas_pb2_grpc.pyi +64 -0
- sift/channels/__init__.py +0 -0
- sift/channels/v2/__init__.py +0 -0
- sift/channels/v2/channels_pb2.py +88 -0
- sift/channels/v2/channels_pb2.pyi +183 -0
- sift/channels/v2/channels_pb2_grpc.py +101 -0
- sift/channels/v2/channels_pb2_grpc.pyi +64 -0
- sift/common/__init__.py +0 -0
- sift/common/type/__init__.py +0 -0
- sift/common/type/v1/__init__.py +0 -0
- sift/common/type/v1/channel_bit_field_element_pb2.py +34 -0
- sift/common/type/v1/channel_bit_field_element_pb2.pyi +33 -0
- sift/common/type/v1/channel_bit_field_element_pb2_grpc.py +4 -0
- sift/common/type/v1/channel_bit_field_element_pb2_grpc.pyi +17 -0
- sift/common/type/v1/channel_data_type_pb2.py +29 -0
- sift/common/type/v1/channel_data_type_pb2.pyi +50 -0
- sift/common/type/v1/channel_data_type_pb2_grpc.py +4 -0
- sift/common/type/v1/channel_data_type_pb2_grpc.pyi +17 -0
- sift/common/type/v1/channel_enum_type_pb2.py +32 -0
- sift/common/type/v1/channel_enum_type_pb2.pyi +29 -0
- sift/common/type/v1/channel_enum_type_pb2_grpc.py +4 -0
- sift/common/type/v1/channel_enum_type_pb2_grpc.pyi +17 -0
- sift/common/type/v1/organization_pb2.py +27 -0
- sift/common/type/v1/organization_pb2.pyi +29 -0
- sift/common/type/v1/organization_pb2_grpc.py +4 -0
- sift/common/type/v1/organization_pb2_grpc.pyi +17 -0
- sift/common/type/v1/resource_identifier_pb2.py +46 -0
- sift/common/type/v1/resource_identifier_pb2.pyi +145 -0
- sift/common/type/v1/resource_identifier_pb2_grpc.py +4 -0
- sift/common/type/v1/resource_identifier_pb2_grpc.pyi +17 -0
- sift/common/type/v1/user_pb2.py +33 -0
- sift/common/type/v1/user_pb2.pyi +36 -0
- sift/common/type/v1/user_pb2_grpc.py +4 -0
- sift/common/type/v1/user_pb2_grpc.pyi +17 -0
- sift/data/__init__.py +0 -0
- sift/data/v1/__init__.py +0 -0
- sift/data/v1/data_pb2.py +212 -0
- sift/data/v1/data_pb2.pyi +745 -0
- sift/data/v1/data_pb2_grpc.py +67 -0
- sift/data/v1/data_pb2_grpc.pyi +44 -0
- sift/ingest/__init__.py +0 -0
- sift/ingest/v1/__init__.py +0 -0
- sift/ingest/v1/ingest_pb2.py +35 -0
- sift/ingest/v1/ingest_pb2.pyi +118 -0
- sift/ingest/v1/ingest_pb2_grpc.py +66 -0
- sift/ingest/v1/ingest_pb2_grpc.pyi +41 -0
- sift/ingestion_configs/__init__.py +0 -0
- sift/ingestion_configs/v1/__init__.py +0 -0
- sift/ingestion_configs/v1/ingestion_configs_pb2.py +115 -0
- sift/ingestion_configs/v1/ingestion_configs_pb2.pyi +332 -0
- sift/ingestion_configs/v1/ingestion_configs_pb2_grpc.py +203 -0
- sift/ingestion_configs/v1/ingestion_configs_pb2_grpc.pyi +124 -0
- sift/notifications/__init__.py +0 -0
- sift/notifications/v1/__init__.py +0 -0
- sift/notifications/v1/notifications_pb2.py +64 -0
- sift/notifications/v1/notifications_pb2.pyi +225 -0
- sift/notifications/v1/notifications_pb2_grpc.py +101 -0
- sift/notifications/v1/notifications_pb2_grpc.pyi +64 -0
- sift/ping/__init__.py +0 -0
- sift/ping/v1/__init__.py +0 -0
- sift/ping/v1/ping_pb2.py +38 -0
- sift/ping/v1/ping_pb2.pyi +36 -0
- sift/ping/v1/ping_pb2_grpc.py +66 -0
- sift/ping/v1/ping_pb2_grpc.pyi +41 -0
- sift/remote_files/__init__.py +0 -0
- sift/remote_files/v1/__init__.py +0 -0
- sift/remote_files/v1/remote_files_pb2.py +174 -0
- sift/remote_files/v1/remote_files_pb2.pyi +472 -0
- sift/remote_files/v1/remote_files_pb2_grpc.py +271 -0
- sift/remote_files/v1/remote_files_pb2_grpc.pyi +164 -0
- sift/report_templates/__init__.py +0 -0
- sift/report_templates/v1/__init__.py +0 -0
- sift/report_templates/v1/report_templates_pb2.py +146 -0
- sift/report_templates/v1/report_templates_pb2.pyi +381 -0
- sift/report_templates/v1/report_templates_pb2_grpc.py +169 -0
- sift/report_templates/v1/report_templates_pb2_grpc.pyi +104 -0
- sift/reports/__init__.py +0 -0
- sift/reports/v1/__init__.py +0 -0
- sift/reports/v1/reports_pb2.py +193 -0
- sift/reports/v1/reports_pb2.pyi +562 -0
- sift/reports/v1/reports_pb2_grpc.py +205 -0
- sift/reports/v1/reports_pb2_grpc.pyi +136 -0
- sift/rule_evaluation/__init__.py +0 -0
- sift/rule_evaluation/v1/__init__.py +0 -0
- sift/rule_evaluation/v1/rule_evaluation_pb2.py +89 -0
- sift/rule_evaluation/v1/rule_evaluation_pb2.pyi +263 -0
- sift/rule_evaluation/v1/rule_evaluation_pb2_grpc.py +101 -0
- sift/rule_evaluation/v1/rule_evaluation_pb2_grpc.pyi +64 -0
- sift/rules/__init__.py +0 -0
- sift/rules/v1/__init__.py +0 -0
- sift/rules/v1/rules_pb2.py +420 -0
- sift/rules/v1/rules_pb2.pyi +1355 -0
- sift/rules/v1/rules_pb2_grpc.py +577 -0
- sift/rules/v1/rules_pb2_grpc.pyi +351 -0
- sift/runs/__init__.py +0 -0
- sift/runs/v2/__init__.py +0 -0
- sift/runs/v2/runs_pb2.py +150 -0
- sift/runs/v2/runs_pb2.pyi +413 -0
- sift/runs/v2/runs_pb2_grpc.py +271 -0
- sift/runs/v2/runs_pb2_grpc.pyi +164 -0
- sift/saved_searches/__init__.py +0 -0
- sift/saved_searches/v1/__init__.py +0 -0
- sift/saved_searches/v1/saved_searches_pb2.py +144 -0
- sift/saved_searches/v1/saved_searches_pb2.pyi +385 -0
- sift/saved_searches/v1/saved_searches_pb2_grpc.py +237 -0
- sift/saved_searches/v1/saved_searches_pb2_grpc.pyi +144 -0
- sift/tags/__init__.py +0 -0
- sift/tags/v1/__init__.py +0 -0
- sift/tags/v1/tags_pb2.py +49 -0
- sift/tags/v1/tags_pb2.pyi +71 -0
- sift/tags/v1/tags_pb2_grpc.py +4 -0
- sift/tags/v1/tags_pb2_grpc.pyi +17 -0
- sift/users/__init__.py +0 -0
- sift/users/v2/__init__.py +0 -0
- sift/users/v2/users_pb2.py +61 -0
- sift/users/v2/users_pb2.pyi +142 -0
- sift/users/v2/users_pb2_grpc.py +135 -0
- sift/users/v2/users_pb2_grpc.pyi +84 -0
- sift/views/__init__.py +0 -0
- sift/views/v1/__init__.py +0 -0
- sift/views/v1/views_pb2.py +130 -0
- sift/views/v1/views_pb2.pyi +466 -0
- sift/views/v1/views_pb2_grpc.py +305 -0
- sift/views/v1/views_pb2_grpc.pyi +184 -0
- sift_grafana/py.typed +0 -0
- sift_grafana/sift_query_model.py +64 -0
- sift_py/__init__.py +923 -0
- sift_py/_internal/__init__.py +5 -0
- sift_py/_internal/cel.py +18 -0
- sift_py/_internal/channel.py +42 -0
- sift_py/_internal/convert/__init__.py +3 -0
- sift_py/_internal/convert/json.py +24 -0
- sift_py/_internal/convert/protobuf.py +34 -0
- sift_py/_internal/convert/timestamp.py +9 -0
- sift_py/_internal/test_util/__init__.py +0 -0
- sift_py/_internal/test_util/channel.py +136 -0
- sift_py/_internal/test_util/fn.py +14 -0
- sift_py/_internal/test_util/server_interceptor.py +62 -0
- sift_py/_internal/time.py +48 -0
- sift_py/_internal/user.py +39 -0
- sift_py/data/__init__.py +171 -0
- sift_py/data/_channel.py +38 -0
- sift_py/data/_deserialize.py +208 -0
- sift_py/data/_deserialize_test.py +134 -0
- sift_py/data/_service_test.py +276 -0
- sift_py/data/_validate.py +10 -0
- sift_py/data/error.py +5 -0
- sift_py/data/query.py +299 -0
- sift_py/data/service.py +497 -0
- sift_py/data_import/__init__.py +130 -0
- sift_py/data_import/_config.py +167 -0
- sift_py/data_import/_config_test.py +166 -0
- sift_py/data_import/_csv_test.py +395 -0
- sift_py/data_import/_status_test.py +176 -0
- sift_py/data_import/_tdms_test.py +238 -0
- sift_py/data_import/ch10.py +157 -0
- sift_py/data_import/config.py +19 -0
- sift_py/data_import/csv.py +259 -0
- sift_py/data_import/status.py +113 -0
- sift_py/data_import/tdms.py +206 -0
- sift_py/data_import/tempfile.py +30 -0
- sift_py/data_import/time_format.py +39 -0
- sift_py/error.py +11 -0
- sift_py/file_attachment/__init__.py +88 -0
- sift_py/file_attachment/_internal/__init__.py +0 -0
- sift_py/file_attachment/_internal/download.py +13 -0
- sift_py/file_attachment/_internal/upload.py +100 -0
- sift_py/file_attachment/_service_test.py +161 -0
- sift_py/file_attachment/entity.py +30 -0
- sift_py/file_attachment/metadata.py +107 -0
- sift_py/file_attachment/service.py +142 -0
- sift_py/grpc/__init__.py +15 -0
- sift_py/grpc/_async_interceptors/__init__.py +0 -0
- sift_py/grpc/_async_interceptors/base.py +72 -0
- sift_py/grpc/_async_interceptors/metadata.py +36 -0
- sift_py/grpc/_interceptors/__init__.py +0 -0
- sift_py/grpc/_interceptors/base.py +61 -0
- sift_py/grpc/_interceptors/context.py +25 -0
- sift_py/grpc/_interceptors/metadata.py +33 -0
- sift_py/grpc/_retry.py +70 -0
- sift_py/grpc/keepalive.py +34 -0
- sift_py/grpc/transport.py +250 -0
- sift_py/grpc/transport_test.py +170 -0
- sift_py/ingestion/__init__.py +6 -0
- sift_py/ingestion/_internal/__init__.py +6 -0
- sift_py/ingestion/_internal/channel.py +12 -0
- sift_py/ingestion/_internal/error.py +10 -0
- sift_py/ingestion/_internal/ingest.py +350 -0
- sift_py/ingestion/_internal/ingest_test.py +357 -0
- sift_py/ingestion/_internal/ingestion_config.py +130 -0
- sift_py/ingestion/_internal/run.py +46 -0
- sift_py/ingestion/_service_test.py +478 -0
- sift_py/ingestion/buffer.py +189 -0
- sift_py/ingestion/channel.py +422 -0
- sift_py/ingestion/config/__init__.py +3 -0
- sift_py/ingestion/config/telemetry.py +281 -0
- sift_py/ingestion/config/telemetry_test.py +405 -0
- sift_py/ingestion/config/yaml/__init__.py +0 -0
- sift_py/ingestion/config/yaml/error.py +44 -0
- sift_py/ingestion/config/yaml/load.py +126 -0
- sift_py/ingestion/config/yaml/spec.py +58 -0
- sift_py/ingestion/config/yaml/test_load.py +25 -0
- sift_py/ingestion/flow.py +73 -0
- sift_py/ingestion/manager.py +99 -0
- sift_py/ingestion/rule/__init__.py +4 -0
- sift_py/ingestion/rule/config.py +11 -0
- sift_py/ingestion/service.py +237 -0
- sift_py/py.typed +0 -0
- sift_py/report_templates/__init__.py +0 -0
- sift_py/report_templates/_config_test.py +34 -0
- sift_py/report_templates/_service_test.py +94 -0
- sift_py/report_templates/config.py +36 -0
- sift_py/report_templates/service.py +171 -0
- sift_py/rest.py +29 -0
- sift_py/rule/__init__.py +0 -0
- sift_py/rule/_config_test.py +109 -0
- sift_py/rule/_service_test.py +168 -0
- sift_py/rule/config.py +229 -0
- sift_py/rule/service.py +484 -0
- sift_py/yaml/__init__.py +0 -0
- sift_py/yaml/_channel_test.py +169 -0
- sift_py/yaml/_rule_test.py +207 -0
- sift_py/yaml/channel.py +224 -0
- sift_py/yaml/report_templates.py +73 -0
- sift_py/yaml/rule.py +321 -0
- sift_py/yaml/utils.py +15 -0
- sift_stack_py-0.3.2.dist-info/LICENSE +7 -0
- sift_stack_py-0.3.2.dist-info/METADATA +109 -0
- sift_stack_py-0.3.2.dist-info/RECORD +291 -0
- sift_stack_py-0.3.2.dist-info/WHEEL +5 -0
- sift_stack_py-0.3.2.dist-info/top_level.txt +5 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from typing import List, Optional, cast
|
|
2
|
+
|
|
3
|
+
from sift.ingestion_configs.v1.ingestion_configs_pb2 import (
|
|
4
|
+
CreateIngestionConfigFlowsRequest,
|
|
5
|
+
CreateIngestionConfigFlowsResponse,
|
|
6
|
+
CreateIngestionConfigRequest,
|
|
7
|
+
CreateIngestionConfigResponse,
|
|
8
|
+
IngestionConfig,
|
|
9
|
+
ListIngestionConfigFlowsRequest,
|
|
10
|
+
ListIngestionConfigFlowsResponse,
|
|
11
|
+
ListIngestionConfigsRequest,
|
|
12
|
+
ListIngestionConfigsResponse,
|
|
13
|
+
)
|
|
14
|
+
from sift.ingestion_configs.v1.ingestion_configs_pb2 import (
|
|
15
|
+
FlowConfig as FlowConfigPb,
|
|
16
|
+
)
|
|
17
|
+
from sift.ingestion_configs.v1.ingestion_configs_pb2_grpc import (
|
|
18
|
+
IngestionConfigServiceStub,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from sift_py.grpc.transport import SiftChannel
|
|
22
|
+
from sift_py.ingestion.flow import FlowConfig
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_ingestion_config_by_client_key(
|
|
26
|
+
channel: SiftChannel,
|
|
27
|
+
client_key: str,
|
|
28
|
+
) -> Optional[IngestionConfig]:
|
|
29
|
+
"""
|
|
30
|
+
Returns `None` if no ingestion config can be matched with the provided `client_key`
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
svc = IngestionConfigServiceStub(channel)
|
|
34
|
+
req = ListIngestionConfigsRequest(
|
|
35
|
+
filter=f'client_key=="{client_key}"',
|
|
36
|
+
page_token="",
|
|
37
|
+
page_size=1,
|
|
38
|
+
)
|
|
39
|
+
res = cast(ListIngestionConfigsResponse, svc.ListIngestionConfigs(req))
|
|
40
|
+
|
|
41
|
+
if len(res.ingestion_configs) == 0:
|
|
42
|
+
return None
|
|
43
|
+
else:
|
|
44
|
+
return res.ingestion_configs[0]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def create_ingestion_config(
|
|
48
|
+
channel: SiftChannel,
|
|
49
|
+
asset_name: str,
|
|
50
|
+
flows: List[FlowConfig],
|
|
51
|
+
client_key: str,
|
|
52
|
+
organization_id: Optional[str],
|
|
53
|
+
) -> IngestionConfig:
|
|
54
|
+
"""
|
|
55
|
+
Creates a new ingestion config
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
svc = IngestionConfigServiceStub(channel)
|
|
59
|
+
req = CreateIngestionConfigRequest(
|
|
60
|
+
asset_name=asset_name,
|
|
61
|
+
client_key=client_key,
|
|
62
|
+
organization_id=organization_id or "",
|
|
63
|
+
flows=[flow.as_pb(FlowConfigPb) for flow in flows],
|
|
64
|
+
)
|
|
65
|
+
res = cast(CreateIngestionConfigResponse, svc.CreateIngestionConfig(req))
|
|
66
|
+
return res.ingestion_config
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_ingestion_config_flow_names(
|
|
70
|
+
channel: SiftChannel,
|
|
71
|
+
ingestion_config_id: str,
|
|
72
|
+
) -> List[str]:
|
|
73
|
+
"""
|
|
74
|
+
Gets all names of flow configs of an ingestion config.
|
|
75
|
+
"""
|
|
76
|
+
flows = get_ingestion_config_flows(channel, ingestion_config_id)
|
|
77
|
+
breakpoint()
|
|
78
|
+
return [flow.name for flow in flows]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def get_ingestion_config_flows(
|
|
82
|
+
channel: SiftChannel, ingestion_config_id: str
|
|
83
|
+
) -> List[FlowConfigPb]:
|
|
84
|
+
svc = IngestionConfigServiceStub(channel)
|
|
85
|
+
|
|
86
|
+
flows: List[FlowConfigPb] = []
|
|
87
|
+
|
|
88
|
+
req = ListIngestionConfigFlowsRequest(
|
|
89
|
+
ingestion_config_id=ingestion_config_id,
|
|
90
|
+
page_size=1_000,
|
|
91
|
+
filter="",
|
|
92
|
+
)
|
|
93
|
+
res = cast(ListIngestionConfigFlowsResponse, svc.ListIngestionConfigFlows(req))
|
|
94
|
+
|
|
95
|
+
for flow in res.flows:
|
|
96
|
+
flows.append(flow)
|
|
97
|
+
|
|
98
|
+
page_token = res.next_page_token
|
|
99
|
+
|
|
100
|
+
while len(page_token) > 0:
|
|
101
|
+
req = ListIngestionConfigFlowsRequest(
|
|
102
|
+
ingestion_config_id=ingestion_config_id,
|
|
103
|
+
page_size=1_000,
|
|
104
|
+
filter="",
|
|
105
|
+
page_token=page_token,
|
|
106
|
+
)
|
|
107
|
+
res = cast(ListIngestionConfigFlowsResponse, svc.ListIngestionConfigFlows(req))
|
|
108
|
+
|
|
109
|
+
for flow in res.flows:
|
|
110
|
+
flows.append(flow)
|
|
111
|
+
|
|
112
|
+
page_token = res.next_page_token
|
|
113
|
+
|
|
114
|
+
return flows
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def create_flow_configs(
|
|
118
|
+
channel: SiftChannel,
|
|
119
|
+
ingestion_config_id: str,
|
|
120
|
+
flow_configs: List[FlowConfig],
|
|
121
|
+
):
|
|
122
|
+
"""
|
|
123
|
+
Adds flow configs to an existing ingestion config.
|
|
124
|
+
"""
|
|
125
|
+
svc = IngestionConfigServiceStub(channel)
|
|
126
|
+
req = CreateIngestionConfigFlowsRequest(
|
|
127
|
+
ingestion_config_id=ingestion_config_id,
|
|
128
|
+
flows=[f.as_pb(FlowConfigPb) for f in flow_configs],
|
|
129
|
+
)
|
|
130
|
+
_ = cast(CreateIngestionConfigFlowsResponse, svc.CreateIngestionConfigFlows(req))
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from typing import List, Optional, cast
|
|
2
|
+
|
|
3
|
+
from sift.runs.v2.runs_pb2 import (
|
|
4
|
+
CreateRunRequest,
|
|
5
|
+
CreateRunResponse,
|
|
6
|
+
ListRunsRequest,
|
|
7
|
+
ListRunsResponse,
|
|
8
|
+
)
|
|
9
|
+
from sift.runs.v2.runs_pb2_grpc import RunServiceStub
|
|
10
|
+
|
|
11
|
+
from sift_py.grpc.transport import SiftChannel
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_run_id_by_name(
|
|
15
|
+
channel: SiftChannel,
|
|
16
|
+
run_name: str,
|
|
17
|
+
) -> Optional[str]:
|
|
18
|
+
svc = RunServiceStub(channel)
|
|
19
|
+
req = ListRunsRequest(
|
|
20
|
+
filter=f'name=="{run_name}"',
|
|
21
|
+
page_size=1,
|
|
22
|
+
)
|
|
23
|
+
res = cast(ListRunsResponse, svc.ListRuns(req))
|
|
24
|
+
|
|
25
|
+
if len(res.runs) == 0:
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
return res.runs[0].run_id
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def create_run(
|
|
32
|
+
channel: SiftChannel,
|
|
33
|
+
run_name: str,
|
|
34
|
+
description: str,
|
|
35
|
+
organization_id: str,
|
|
36
|
+
tags: List[str],
|
|
37
|
+
) -> str:
|
|
38
|
+
svc = RunServiceStub(channel)
|
|
39
|
+
req = CreateRunRequest(
|
|
40
|
+
name=run_name,
|
|
41
|
+
description=description,
|
|
42
|
+
organization_id=organization_id,
|
|
43
|
+
tags=tags,
|
|
44
|
+
)
|
|
45
|
+
res = cast(CreateRunResponse, svc.CreateRun(req))
|
|
46
|
+
return res.run.run_id
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
import random
|
|
2
|
+
from contextlib import contextmanager
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from time import sleep
|
|
5
|
+
from typing import Callable, List
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from pytest_mock import MockFixture
|
|
9
|
+
from sift.ingest.v1.ingest_pb2 import IngestWithConfigDataStreamRequest
|
|
10
|
+
from sift.ingestion_configs.v1.ingestion_configs_pb2 import FlowConfig as FlowConfigPb
|
|
11
|
+
from sift.ingestion_configs.v1.ingestion_configs_pb2 import IngestionConfig as IngestionConfigPb
|
|
12
|
+
|
|
13
|
+
import sift_py.ingestion._internal.ingest
|
|
14
|
+
from sift_py._internal.test_util.channel import MockChannel
|
|
15
|
+
from sift_py._internal.test_util.fn import _mock_path as _mock_path_imp
|
|
16
|
+
from sift_py.ingestion._internal.error import IngestionValidationError
|
|
17
|
+
from sift_py.ingestion._internal.ingestion_config import (
|
|
18
|
+
create_flow_configs,
|
|
19
|
+
create_ingestion_config,
|
|
20
|
+
get_ingestion_config_by_client_key,
|
|
21
|
+
get_ingestion_config_flows,
|
|
22
|
+
)
|
|
23
|
+
from sift_py.ingestion.channel import ChannelConfig, ChannelDataType, double_value
|
|
24
|
+
from sift_py.ingestion.config.telemetry import TelemetryConfig
|
|
25
|
+
from sift_py.ingestion.flow import FlowConfig
|
|
26
|
+
from sift_py.ingestion.service import IngestionService
|
|
27
|
+
|
|
28
|
+
_mock_path = _mock_path_imp(sift_py.ingestion._internal.ingest)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_ingestion_service_buffered_ingestion(mocker: MockFixture):
|
|
32
|
+
"""
|
|
33
|
+
Ensures that the ingestion method is being called the expected amount of times
|
|
34
|
+
when using the buffered method of ingestion.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
mock_ingest = mocker.patch.object(IngestionService, "ingest")
|
|
38
|
+
mock_ingest.return_value = None
|
|
39
|
+
|
|
40
|
+
readings_flow = FlowConfig(
|
|
41
|
+
name="readings",
|
|
42
|
+
channels=[
|
|
43
|
+
ChannelConfig(
|
|
44
|
+
name="my-channel",
|
|
45
|
+
data_type=ChannelDataType.DOUBLE,
|
|
46
|
+
),
|
|
47
|
+
],
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
telemetry_config = TelemetryConfig(
|
|
51
|
+
asset_name="my-asset",
|
|
52
|
+
ingestion_client_key="ingestion-client-key",
|
|
53
|
+
flows=[readings_flow],
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
mock_ingestion_config = IngestionConfigPb(
|
|
57
|
+
ingestion_config_id="ingestion-config-id",
|
|
58
|
+
asset_id="asset-id",
|
|
59
|
+
client_key="client-key",
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
mock_get_ingestion_config_by_client_key = mocker.patch(
|
|
63
|
+
_mock_path(get_ingestion_config_by_client_key)
|
|
64
|
+
)
|
|
65
|
+
mock_get_ingestion_config_by_client_key.return_value = mock_ingestion_config
|
|
66
|
+
|
|
67
|
+
mock_get_ingestion_config_flows = mocker.patch(_mock_path(get_ingestion_config_flows))
|
|
68
|
+
mock_get_ingestion_config_flows.return_value = [readings_flow.as_pb(FlowConfigPb)]
|
|
69
|
+
|
|
70
|
+
ingestion_service = IngestionService(MockChannel(), telemetry_config)
|
|
71
|
+
|
|
72
|
+
@contextmanager
|
|
73
|
+
def mock_ctx_manager():
|
|
74
|
+
yield
|
|
75
|
+
mock_ingest.reset_mock()
|
|
76
|
+
|
|
77
|
+
with mock_ctx_manager():
|
|
78
|
+
with ingestion_service.buffered_ingestion() as buffered_ingestion:
|
|
79
|
+
assert buffered_ingestion._buffer_size == 1_000
|
|
80
|
+
|
|
81
|
+
for _ in range(10_000):
|
|
82
|
+
buffered_ingestion.try_ingest_flows(
|
|
83
|
+
{
|
|
84
|
+
"flow_name": "readings",
|
|
85
|
+
"timestamp": datetime.now(timezone.utc),
|
|
86
|
+
"channel_values": [
|
|
87
|
+
{"channel_name": "my-channel", "value": double_value(random.random())}
|
|
88
|
+
],
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
assert mock_ingest.call_count == 10
|
|
92
|
+
assert len(buffered_ingestion._buffer) == 0
|
|
93
|
+
|
|
94
|
+
# No additional buffered items so no need for an extra ingest call
|
|
95
|
+
assert mock_ingest.call_count == 10
|
|
96
|
+
|
|
97
|
+
with mock_ctx_manager():
|
|
98
|
+
with ingestion_service.buffered_ingestion() as buffered_ingestion:
|
|
99
|
+
assert buffered_ingestion._buffer_size == 1_000
|
|
100
|
+
|
|
101
|
+
for _ in range(10_500):
|
|
102
|
+
buffered_ingestion.try_ingest_flows(
|
|
103
|
+
{
|
|
104
|
+
"flow_name": "readings",
|
|
105
|
+
"timestamp": datetime.now(timezone.utc),
|
|
106
|
+
"channel_values": [
|
|
107
|
+
{"channel_name": "my-channel", "value": double_value(random.random())}
|
|
108
|
+
],
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
assert mock_ingest.call_count == 10
|
|
113
|
+
assert len(buffered_ingestion._buffer) == 500
|
|
114
|
+
|
|
115
|
+
# Exiting the context manager should call flush one more time
|
|
116
|
+
assert mock_ingest.call_count == 11
|
|
117
|
+
|
|
118
|
+
with mock_ctx_manager():
|
|
119
|
+
with ingestion_service.buffered_ingestion(500) as buffered_ingestion:
|
|
120
|
+
assert buffered_ingestion._buffer_size == 500
|
|
121
|
+
|
|
122
|
+
for _ in range(5_200):
|
|
123
|
+
buffered_ingestion.try_ingest_flows(
|
|
124
|
+
{
|
|
125
|
+
"flow_name": "readings",
|
|
126
|
+
"timestamp": datetime.now(timezone.utc),
|
|
127
|
+
"channel_values": [
|
|
128
|
+
{"channel_name": "my-channel", "value": double_value(random.random())}
|
|
129
|
+
],
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
assert mock_ingest.call_count == 10
|
|
134
|
+
assert len(buffered_ingestion._buffer) == 200
|
|
135
|
+
|
|
136
|
+
assert mock_ingest.call_count == 11
|
|
137
|
+
|
|
138
|
+
with mock_ctx_manager():
|
|
139
|
+
with ingestion_service.buffered_ingestion(800) as buffered_ingestion:
|
|
140
|
+
for _ in range(5_200):
|
|
141
|
+
buffered_ingestion.ingest_flows(
|
|
142
|
+
{
|
|
143
|
+
"flow_name": "readings",
|
|
144
|
+
"timestamp": datetime.now(timezone.utc),
|
|
145
|
+
"channel_values": [double_value(random.random())],
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
assert mock_ingest.call_count == 6
|
|
150
|
+
assert len(buffered_ingestion._buffer) == 400
|
|
151
|
+
|
|
152
|
+
assert mock_ingest.call_count == 7
|
|
153
|
+
|
|
154
|
+
with mock_ctx_manager():
|
|
155
|
+
with ingestion_service.buffered_ingestion() as buffered_ingestion:
|
|
156
|
+
for _ in range(6_000):
|
|
157
|
+
buffered_ingestion.ingest_flows(
|
|
158
|
+
{
|
|
159
|
+
"flow_name": "readings",
|
|
160
|
+
"timestamp": datetime.now(timezone.utc),
|
|
161
|
+
"channel_values": [double_value(random.random())],
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
assert mock_ingest.call_count == 6
|
|
166
|
+
assert len(buffered_ingestion._buffer) == 0
|
|
167
|
+
|
|
168
|
+
assert mock_ingest.call_count == 6
|
|
169
|
+
|
|
170
|
+
with mock_ctx_manager():
|
|
171
|
+
with ingestion_service.buffered_ingestion() as buffered_ingestion:
|
|
172
|
+
for _ in range(6_600):
|
|
173
|
+
buffered_ingestion.ingest_flows(
|
|
174
|
+
{
|
|
175
|
+
"flow_name": "readings",
|
|
176
|
+
"timestamp": datetime.now(timezone.utc),
|
|
177
|
+
"channel_values": [double_value(random.random())],
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
assert mock_ingest.call_count == 6
|
|
182
|
+
assert len(buffered_ingestion._buffer) == 600
|
|
183
|
+
|
|
184
|
+
with pytest.raises(Exception):
|
|
185
|
+
raise
|
|
186
|
+
|
|
187
|
+
assert len(buffered_ingestion._buffer) == 0
|
|
188
|
+
assert mock_ingest.call_count == 7
|
|
189
|
+
|
|
190
|
+
with mock_ctx_manager():
|
|
191
|
+
on_error_spy = mocker.stub()
|
|
192
|
+
|
|
193
|
+
def on_error(
|
|
194
|
+
err: BaseException, requests: List[IngestWithConfigDataStreamRequest], _flush: Callable
|
|
195
|
+
):
|
|
196
|
+
on_error_spy()
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
with pytest.raises(Exception):
|
|
200
|
+
with ingestion_service.buffered_ingestion(on_error=on_error) as buffered_ingestion:
|
|
201
|
+
for _ in range(6_600):
|
|
202
|
+
buffered_ingestion.ingest_flows(
|
|
203
|
+
{
|
|
204
|
+
"flow_name": "readings",
|
|
205
|
+
"timestamp": datetime.now(timezone.utc),
|
|
206
|
+
"channel_values": [double_value(random.random())],
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
raise
|
|
210
|
+
|
|
211
|
+
on_error_spy.assert_called_once()
|
|
212
|
+
assert len(buffered_ingestion._buffer) == 600
|
|
213
|
+
assert mock_ingest.call_count == 6
|
|
214
|
+
|
|
215
|
+
with mock_ctx_manager():
|
|
216
|
+
on_error_flush_spy = mocker.stub()
|
|
217
|
+
|
|
218
|
+
def on_error(
|
|
219
|
+
err: BaseException, requests: List[IngestWithConfigDataStreamRequest], _flush: Callable
|
|
220
|
+
):
|
|
221
|
+
on_error_flush_spy()
|
|
222
|
+
_flush()
|
|
223
|
+
pass
|
|
224
|
+
|
|
225
|
+
with pytest.raises(Exception):
|
|
226
|
+
with ingestion_service.buffered_ingestion(on_error=on_error) as buffered_ingestion:
|
|
227
|
+
for _ in range(6_600):
|
|
228
|
+
buffered_ingestion.ingest_flows(
|
|
229
|
+
{
|
|
230
|
+
"flow_name": "readings",
|
|
231
|
+
"timestamp": datetime.now(timezone.utc),
|
|
232
|
+
"channel_values": [double_value(random.random())],
|
|
233
|
+
}
|
|
234
|
+
)
|
|
235
|
+
raise
|
|
236
|
+
|
|
237
|
+
on_error_spy.assert_called_once()
|
|
238
|
+
assert len(buffered_ingestion._buffer) == 0
|
|
239
|
+
assert mock_ingest.call_count == 7
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def test_ingestion_service_modify_existing_channel_configs(mocker: MockFixture):
|
|
243
|
+
"""
|
|
244
|
+
Tests modifying existing channel configs in telemetry config. If a channel config
|
|
245
|
+
is modified in a telemetry config after it has already been used for ingestion
|
|
246
|
+
then we should create a new flow. If a user modifies a channel back to a previous
|
|
247
|
+
version (same component and name), then we should re-use an existing channel.
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
mock_ingestion_config = IngestionConfigPb(
|
|
251
|
+
ingestion_config_id="my-ingestion-config-id",
|
|
252
|
+
client_key="my-ingestion-config",
|
|
253
|
+
asset_id="my-asset-id",
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
channel_a = ChannelConfig(
|
|
257
|
+
name="channel_a",
|
|
258
|
+
component="A",
|
|
259
|
+
data_type=ChannelDataType.DOUBLE,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
flow_a = FlowConfig(
|
|
263
|
+
name="flow_a",
|
|
264
|
+
channels=[channel_a],
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
telemetry_config = TelemetryConfig(
|
|
268
|
+
asset_name="my-asset-name",
|
|
269
|
+
ingestion_client_key=mock_ingestion_config.ingestion_config_id,
|
|
270
|
+
flows=[flow_a],
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
mock_get_ingestion_config_by_client_key = mocker.patch(
|
|
274
|
+
_mock_path(get_ingestion_config_by_client_key)
|
|
275
|
+
)
|
|
276
|
+
mock_get_ingestion_config_by_client_key.return_value = None
|
|
277
|
+
|
|
278
|
+
mock_create_ingestion_config = mocker.patch(_mock_path(create_ingestion_config))
|
|
279
|
+
mock_create_ingestion_config.return_value = mock_ingestion_config
|
|
280
|
+
|
|
281
|
+
mock_get_ingestion_config_flows = mocker.patch(_mock_path(get_ingestion_config_flows))
|
|
282
|
+
mock_get_ingestion_config_flows.return_value = [flow_a.as_pb(FlowConfigPb)]
|
|
283
|
+
|
|
284
|
+
mock_channel = MockChannel()
|
|
285
|
+
|
|
286
|
+
ingestion_service = IngestionService(
|
|
287
|
+
channel=mock_channel,
|
|
288
|
+
config=telemetry_config,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
mock_create_ingestion_config.assert_called_once_with(
|
|
292
|
+
mock_channel,
|
|
293
|
+
telemetry_config.asset_name,
|
|
294
|
+
telemetry_config.flows,
|
|
295
|
+
telemetry_config.ingestion_client_key,
|
|
296
|
+
None,
|
|
297
|
+
)
|
|
298
|
+
assert ingestion_service.flow_configs_by_name[flow_a.name].channels[0] == channel_a
|
|
299
|
+
|
|
300
|
+
# Modify an existing channel but don't modify flow
|
|
301
|
+
channel_a.data_type = ChannelDataType.STRING
|
|
302
|
+
|
|
303
|
+
mock_create_flow_configs = mocker.patch(_mock_path(create_flow_configs))
|
|
304
|
+
mock_create_flow_configs.return_value = None
|
|
305
|
+
|
|
306
|
+
mock_get_ingestion_config_by_client_key.reset_mock()
|
|
307
|
+
mock_get_ingestion_config_by_client_key.return_value = mock_ingestion_config
|
|
308
|
+
|
|
309
|
+
# Re-initialize ingestion service
|
|
310
|
+
ingestion_service = IngestionService(
|
|
311
|
+
channel=mock_channel,
|
|
312
|
+
config=telemetry_config,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
# Assert that we are trying to create a new flow with the same name as `flow_a`
|
|
316
|
+
# but with a new channel.
|
|
317
|
+
mock_create_flow_configs.assert_called_once_with(
|
|
318
|
+
mock_channel, mock_ingestion_config.ingestion_config_id, [flow_a]
|
|
319
|
+
)
|
|
320
|
+
assert ingestion_service.flow_configs_by_name[flow_a.name].channels[0] == channel_a
|
|
321
|
+
|
|
322
|
+
# Okay now what happens if someone were to change the channel config back to the original..
|
|
323
|
+
|
|
324
|
+
# Modify back to original
|
|
325
|
+
channel_a.data_type = ChannelDataType.DOUBLE
|
|
326
|
+
|
|
327
|
+
mock_create_flow_configs.reset_mock()
|
|
328
|
+
|
|
329
|
+
# Re-initialize ingestion service
|
|
330
|
+
ingestion_service = IngestionService(
|
|
331
|
+
channel=mock_channel,
|
|
332
|
+
config=telemetry_config,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
# We shouldn't be creating a new flow, should re-use an existing flow.
|
|
336
|
+
mock_create_flow_configs.assert_not_called()
|
|
337
|
+
assert ingestion_service.flow_configs_by_name[flow_a.name].channels[0] == channel_a
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def test_ingestion_service_register_new_flow(mocker: MockFixture):
|
|
341
|
+
mock_ingestion_config = IngestionConfigPb(
|
|
342
|
+
ingestion_config_id="my-ingestion-config-id",
|
|
343
|
+
client_key="my-ingestion-config",
|
|
344
|
+
asset_id="my-asset-id",
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
channel_a = ChannelConfig(
|
|
348
|
+
name="channel_a",
|
|
349
|
+
component="A",
|
|
350
|
+
data_type=ChannelDataType.DOUBLE,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
flow_a = FlowConfig(
|
|
354
|
+
name="flow_a",
|
|
355
|
+
channels=[channel_a],
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
telemetry_config = TelemetryConfig(
|
|
359
|
+
asset_name="my-asset-name",
|
|
360
|
+
ingestion_client_key=mock_ingestion_config.ingestion_config_id,
|
|
361
|
+
flows=[flow_a],
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
mock_get_ingestion_config_by_client_key = mocker.patch(
|
|
365
|
+
_mock_path(get_ingestion_config_by_client_key)
|
|
366
|
+
)
|
|
367
|
+
mock_get_ingestion_config_by_client_key.return_value = None
|
|
368
|
+
|
|
369
|
+
mock_create_ingestion_config = mocker.patch(_mock_path(create_ingestion_config))
|
|
370
|
+
mock_create_ingestion_config.return_value = mock_ingestion_config
|
|
371
|
+
|
|
372
|
+
mock_get_ingestion_config_flows = mocker.patch(_mock_path(get_ingestion_config_flows))
|
|
373
|
+
mock_get_ingestion_config_flows.return_value = [flow_a.as_pb(FlowConfigPb)]
|
|
374
|
+
|
|
375
|
+
mock_channel = MockChannel()
|
|
376
|
+
|
|
377
|
+
ingestion_service = IngestionService(
|
|
378
|
+
channel=mock_channel,
|
|
379
|
+
config=telemetry_config,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
new_flow_config = FlowConfig(
|
|
383
|
+
name="my_new_flow", channels=[ChannelConfig("new_channel", ChannelDataType.DOUBLE)]
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
mock_create_flow_configs = mocker.patch(_mock_path(create_flow_configs))
|
|
387
|
+
mock_create_flow_configs.return_value = None
|
|
388
|
+
|
|
389
|
+
assert ingestion_service.flow_configs_by_name.get("my_new_flow") is None
|
|
390
|
+
|
|
391
|
+
ingestion_service.try_create_flow(new_flow_config)
|
|
392
|
+
|
|
393
|
+
mock_create_flow_configs.assert_called_once_with(
|
|
394
|
+
mock_channel, mock_ingestion_config.ingestion_config_id, [new_flow_config]
|
|
395
|
+
)
|
|
396
|
+
assert ingestion_service.flow_configs_by_name["my_new_flow"] == new_flow_config
|
|
397
|
+
|
|
398
|
+
# Test the name collision
|
|
399
|
+
new_flow_config_name_collision = FlowConfig(
|
|
400
|
+
name="my_new_flow", channels=[ChannelConfig("foobar", ChannelDataType.DOUBLE)]
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
with pytest.raises(IngestionValidationError):
|
|
404
|
+
ingestion_service.try_create_flow(new_flow_config_name_collision)
|
|
405
|
+
|
|
406
|
+
# Bypass the validation
|
|
407
|
+
ingestion_service.create_flow(new_flow_config_name_collision)
|
|
408
|
+
assert ingestion_service.flow_configs_by_name["my_new_flow"] == new_flow_config_name_collision
|
|
409
|
+
assert ingestion_service.flow_configs_by_name["my_new_flow"] != new_flow_config
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def test_ingestion_service_buffered_ingestion_flush_timeout(mocker: MockFixture):
|
|
413
|
+
"""
|
|
414
|
+
Test for timeout based flush mechanism in buffered ingestion. If buffer hasn't been flushed
|
|
415
|
+
after a certain time then the buffer will be automatically flushed.
|
|
416
|
+
"""
|
|
417
|
+
|
|
418
|
+
mock_ingest = mocker.patch.object(IngestionService, "ingest")
|
|
419
|
+
mock_ingest.return_value = None
|
|
420
|
+
|
|
421
|
+
readings_flow = FlowConfig(
|
|
422
|
+
name="readings",
|
|
423
|
+
channels=[
|
|
424
|
+
ChannelConfig(
|
|
425
|
+
name="my-channel",
|
|
426
|
+
data_type=ChannelDataType.DOUBLE,
|
|
427
|
+
),
|
|
428
|
+
],
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
telemetry_config = TelemetryConfig(
|
|
432
|
+
asset_name="my-asset",
|
|
433
|
+
ingestion_client_key="ingestion-client-key",
|
|
434
|
+
flows=[readings_flow],
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
mock_ingestion_config = IngestionConfigPb(
|
|
438
|
+
ingestion_config_id="ingestion-config-id",
|
|
439
|
+
asset_id="asset-id",
|
|
440
|
+
client_key="client-key",
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
mock_get_ingestion_config_by_client_key = mocker.patch(
|
|
444
|
+
_mock_path(get_ingestion_config_by_client_key)
|
|
445
|
+
)
|
|
446
|
+
mock_get_ingestion_config_by_client_key.return_value = mock_ingestion_config
|
|
447
|
+
|
|
448
|
+
mock_get_ingestion_config_flows = mocker.patch(_mock_path(get_ingestion_config_flows))
|
|
449
|
+
mock_get_ingestion_config_flows.return_value = [readings_flow.as_pb(FlowConfigPb)]
|
|
450
|
+
|
|
451
|
+
ingestion_service = IngestionService(MockChannel(), telemetry_config)
|
|
452
|
+
|
|
453
|
+
@contextmanager
|
|
454
|
+
def mock_ctx_manager():
|
|
455
|
+
yield
|
|
456
|
+
mock_ingest.reset_mock()
|
|
457
|
+
|
|
458
|
+
with mock_ctx_manager():
|
|
459
|
+
with ingestion_service.buffered_ingestion(flush_interval_sec=2) as buffered_ingestion:
|
|
460
|
+
assert buffered_ingestion._buffer_size == 1_000
|
|
461
|
+
|
|
462
|
+
for _ in range(1_500):
|
|
463
|
+
buffered_ingestion.try_ingest_flows(
|
|
464
|
+
{
|
|
465
|
+
"flow_name": "readings",
|
|
466
|
+
"timestamp": datetime.now(timezone.utc),
|
|
467
|
+
"channel_values": [
|
|
468
|
+
{"channel_name": "my-channel", "value": double_value(random.random())}
|
|
469
|
+
],
|
|
470
|
+
}
|
|
471
|
+
)
|
|
472
|
+
assert mock_ingest.call_count == 1
|
|
473
|
+
assert len(buffered_ingestion._buffer) == 500
|
|
474
|
+
|
|
475
|
+
# This will cause the flush timer to flush based on provided interval
|
|
476
|
+
sleep(5)
|
|
477
|
+
assert mock_ingest.call_count == 2
|
|
478
|
+
assert len(buffered_ingestion._buffer) == 0
|