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,167 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List, Optional, Type, Union
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, field_validator, model_validator
|
|
6
|
+
from pydantic_core import PydanticCustomError
|
|
7
|
+
from typing_extensions import Self
|
|
8
|
+
|
|
9
|
+
from sift_py.data_import.time_format import TimeFormatType
|
|
10
|
+
from sift_py.ingestion.channel import ChannelBitFieldElement, ChannelDataType, ChannelEnumType
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ConfigBaseModel(BaseModel):
|
|
14
|
+
"""
|
|
15
|
+
Specialized BaseMode that forbids extra fields.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
model_config = ConfigDict(extra="forbid")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CsvConfigImpl(ConfigBaseModel):
|
|
22
|
+
"""
|
|
23
|
+
Defines the CSV config spec.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
asset_name: str
|
|
27
|
+
run_name: str = ""
|
|
28
|
+
run_id: str = ""
|
|
29
|
+
first_data_row: int
|
|
30
|
+
time_column: TimeColumn
|
|
31
|
+
data_columns: Dict[int, DataColumn]
|
|
32
|
+
|
|
33
|
+
@model_validator(mode="after")
|
|
34
|
+
def validate_config(self) -> Self:
|
|
35
|
+
if not self.data_columns:
|
|
36
|
+
raise PydanticCustomError("invalid_config_error", "Empty 'data_columns'")
|
|
37
|
+
return self
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class EnumType(ConfigBaseModel, ChannelEnumType):
|
|
41
|
+
"""
|
|
42
|
+
Defines an enum entry in the CSV config.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class BitFieldElement(ConfigBaseModel, ChannelBitFieldElement):
|
|
47
|
+
"""
|
|
48
|
+
Defines a bit field element entry in the CSV config.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TimeColumn(ConfigBaseModel):
|
|
53
|
+
"""
|
|
54
|
+
Defines a time column entry in the CSV config.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
format: Union[str, TimeFormatType]
|
|
58
|
+
column_number: int
|
|
59
|
+
relative_start_time: Optional[str] = None
|
|
60
|
+
|
|
61
|
+
@field_validator("format", mode="before")
|
|
62
|
+
@classmethod
|
|
63
|
+
def convert_format(cls, raw: Union[str, TimeFormatType]) -> str:
|
|
64
|
+
"""
|
|
65
|
+
Converts the provided format value to a string.
|
|
66
|
+
"""
|
|
67
|
+
if isinstance(raw, TimeFormatType):
|
|
68
|
+
return raw.as_human_str()
|
|
69
|
+
elif isinstance(raw, str):
|
|
70
|
+
value = TimeFormatType.from_str(raw)
|
|
71
|
+
if value is not None:
|
|
72
|
+
return value.as_human_str()
|
|
73
|
+
|
|
74
|
+
raise PydanticCustomError("invalid_config_error", f"Invalid time format: {raw}.")
|
|
75
|
+
|
|
76
|
+
@model_validator(mode="after")
|
|
77
|
+
def validate_time(self) -> Self:
|
|
78
|
+
"""
|
|
79
|
+
Validates the provided time format.
|
|
80
|
+
"""
|
|
81
|
+
format = TimeFormatType.from_str(self.format) # type: ignore
|
|
82
|
+
if format is None:
|
|
83
|
+
raise PydanticCustomError(
|
|
84
|
+
"invalid_config_error", f"Invalid time format: {self.format}."
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if format.is_relative():
|
|
88
|
+
if self.relative_start_time is None:
|
|
89
|
+
raise PydanticCustomError("invalid_config_error", "Missing 'relative_start_time'")
|
|
90
|
+
else:
|
|
91
|
+
if self.relative_start_time is not None:
|
|
92
|
+
raise PydanticCustomError(
|
|
93
|
+
"invalid_config_error",
|
|
94
|
+
"'relative_start_time' specified for non relative time format.",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return self
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class DataColumn(ConfigBaseModel):
|
|
101
|
+
"""
|
|
102
|
+
Defines a data column entry in the CSV config.
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
name: str
|
|
106
|
+
data_type: Union[str, ChannelDataType, Type]
|
|
107
|
+
component: str = ""
|
|
108
|
+
units: str = ""
|
|
109
|
+
description: str = ""
|
|
110
|
+
# Only valid if data_type is "CHANNEL_DATA_TYPE_ENUM".
|
|
111
|
+
enum_types: List[EnumType] = []
|
|
112
|
+
# Only valid if data_type is "CHANNEL_DATA_TYPE_BIT_FIELD"
|
|
113
|
+
bit_field_elements: List[BitFieldElement] = []
|
|
114
|
+
|
|
115
|
+
@field_validator("data_type", mode="before")
|
|
116
|
+
@classmethod
|
|
117
|
+
def convert_data_type(cls, raw: Union[str, ChannelDataType, Type]) -> str:
|
|
118
|
+
"""
|
|
119
|
+
Converts the provided data_type value to a string.
|
|
120
|
+
"""
|
|
121
|
+
if isinstance(raw, type):
|
|
122
|
+
if raw == int:
|
|
123
|
+
return ChannelDataType.INT_64.as_human_str(api_format=True)
|
|
124
|
+
elif raw == float:
|
|
125
|
+
return ChannelDataType.DOUBLE.as_human_str(api_format=True)
|
|
126
|
+
elif raw == str:
|
|
127
|
+
return ChannelDataType.STRING.as_human_str(api_format=True)
|
|
128
|
+
elif raw == bool:
|
|
129
|
+
return ChannelDataType.BOOL.as_human_str(api_format=True)
|
|
130
|
+
elif isinstance(raw, ChannelDataType):
|
|
131
|
+
return raw.as_human_str(api_format=True)
|
|
132
|
+
elif isinstance(raw, str):
|
|
133
|
+
value = ChannelDataType.from_str(raw)
|
|
134
|
+
if value is not None:
|
|
135
|
+
return value.as_human_str(api_format=True)
|
|
136
|
+
|
|
137
|
+
raise PydanticCustomError("invalid_config_error", f"Invalid data_type: {raw}.")
|
|
138
|
+
|
|
139
|
+
@model_validator(mode="after")
|
|
140
|
+
def validate_enums(self) -> Self:
|
|
141
|
+
"""
|
|
142
|
+
Validates the enum configuration.
|
|
143
|
+
"""
|
|
144
|
+
data_type = ChannelDataType.from_str(self.data_type) # type: ignore
|
|
145
|
+
if self.enum_types:
|
|
146
|
+
if data_type != ChannelDataType.ENUM:
|
|
147
|
+
raise PydanticCustomError(
|
|
148
|
+
"invalid_config_error",
|
|
149
|
+
f"Enums can only be specified with the CHANNEL_DATA_TYPE_ENUM data type. {self.name} is {self.data_type}",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
return self
|
|
153
|
+
|
|
154
|
+
@model_validator(mode="after")
|
|
155
|
+
def validate_bit_fields(self) -> Self:
|
|
156
|
+
"""
|
|
157
|
+
Validates the bit field configuration.
|
|
158
|
+
"""
|
|
159
|
+
data_type = ChannelDataType.from_str(self.data_type) # type: ignore
|
|
160
|
+
if self.bit_field_elements:
|
|
161
|
+
if data_type != ChannelDataType.BIT_FIELD:
|
|
162
|
+
raise PydanticCustomError(
|
|
163
|
+
"invalid_config_error",
|
|
164
|
+
f"Bit fields can only be specified with the CHANNEL_DATA_TYPE_BIT_FIELD data type. {self.name} is {self.data_type}",
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
return self
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from sift_py.data_import.config import CsvConfig
|
|
4
|
+
from sift_py.data_import.time_format import TimeFormatType
|
|
5
|
+
from sift_py.ingestion.channel import ChannelDataType
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.fixture
|
|
9
|
+
def csv_config_data():
|
|
10
|
+
return {
|
|
11
|
+
"asset_name": "test_asset",
|
|
12
|
+
"first_data_row": 2,
|
|
13
|
+
"time_column": {
|
|
14
|
+
"format": "TIME_FORMAT_ABSOLUTE_DATETIME",
|
|
15
|
+
"column_number": 1,
|
|
16
|
+
},
|
|
17
|
+
"data_columns": {
|
|
18
|
+
1: {
|
|
19
|
+
"name": "channel",
|
|
20
|
+
"data_type": "CHANNEL_DATA_TYPE_INT_32",
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_empty_data_columns(csv_config_data: dict):
|
|
27
|
+
csv_config_data["data_columns"] = {}
|
|
28
|
+
with pytest.raises(Exception, match="Empty 'data_columns'"):
|
|
29
|
+
CsvConfig(csv_config_data)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_data_column_validation(csv_config_data: dict):
|
|
33
|
+
csv_config_data["data_columns"] = {
|
|
34
|
+
1: {
|
|
35
|
+
"name": "channel",
|
|
36
|
+
"data_type": "INVALID_DATA_TYPE",
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
with pytest.raises(Exception, match="Invalid data_type:"):
|
|
40
|
+
CsvConfig(csv_config_data)
|
|
41
|
+
|
|
42
|
+
csv_config_data["data_columns"] = {1: {"name": "channel", "data_type": complex}}
|
|
43
|
+
with pytest.raises(Exception, match="Invalid data_type:"):
|
|
44
|
+
CsvConfig(csv_config_data)
|
|
45
|
+
|
|
46
|
+
csv_config_data["data_columns"] = {
|
|
47
|
+
1: {"name": "channel_bool", "data_type": ChannelDataType.BOOL},
|
|
48
|
+
2: {"name": "channel_double", "data_type": ChannelDataType.DOUBLE},
|
|
49
|
+
3: {"name": "channel_int", "data_type": ChannelDataType.INT_64},
|
|
50
|
+
4: {"name": "channel_str", "data_type": ChannelDataType.STRING},
|
|
51
|
+
}
|
|
52
|
+
CsvConfig(csv_config_data)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_enums(csv_config_data: dict):
|
|
56
|
+
csv_config_data["data_columns"] = {
|
|
57
|
+
1: {
|
|
58
|
+
"name": "channel",
|
|
59
|
+
"data_type": "CHANNEL_DATA_TYPE_INT_32",
|
|
60
|
+
"enum_types": [
|
|
61
|
+
{"key": 1, "name": "value_1"},
|
|
62
|
+
{"key": 2, "name": "value_2"},
|
|
63
|
+
],
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
with pytest.raises(Exception, match="Enums can only be specified"):
|
|
67
|
+
CsvConfig(csv_config_data)
|
|
68
|
+
|
|
69
|
+
csv_config_data["data_columns"] = {
|
|
70
|
+
1: {
|
|
71
|
+
"name": "channel",
|
|
72
|
+
"data_type": "CHANNEL_DATA_TYPE_ENUM",
|
|
73
|
+
"enum_types": [
|
|
74
|
+
{"key": 1, "name": "value_1", "extra_key": "value"},
|
|
75
|
+
{"key": 2, "name": "value_2"},
|
|
76
|
+
],
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
with pytest.raises(Exception, match="validation error"):
|
|
80
|
+
CsvConfig(csv_config_data)
|
|
81
|
+
|
|
82
|
+
csv_config_data["data_columns"] = {
|
|
83
|
+
1: {
|
|
84
|
+
"name": "channel",
|
|
85
|
+
"data_type": "CHANNEL_DATA_TYPE_ENUM",
|
|
86
|
+
"enum_types": [
|
|
87
|
+
{"key": 1, "name": "value_1"},
|
|
88
|
+
{"key": 2, "name": "value_2"},
|
|
89
|
+
],
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
CsvConfig(csv_config_data)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_bit_field(csv_config_data: dict):
|
|
96
|
+
csv_config_data["data_columns"] = {
|
|
97
|
+
1: {
|
|
98
|
+
"name": "channel",
|
|
99
|
+
"data_type": "CHANNEL_DATA_TYPE_INT_32",
|
|
100
|
+
"bit_field_elements": [
|
|
101
|
+
{"index": 1, "name": "bit_field_name_1", "bit_count": 4},
|
|
102
|
+
],
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
with pytest.raises(Exception, match="Bit fields can only be specified"):
|
|
106
|
+
CsvConfig(csv_config_data)
|
|
107
|
+
|
|
108
|
+
csv_config_data["data_columns"] = {
|
|
109
|
+
1: {
|
|
110
|
+
"name": "channel",
|
|
111
|
+
"data_type": "CHANNEL_DATA_TYPE_INT_32",
|
|
112
|
+
"bit_field_elements": [
|
|
113
|
+
{
|
|
114
|
+
"index": 1,
|
|
115
|
+
"name": "bit_field_name_1",
|
|
116
|
+
"bit_count": 4,
|
|
117
|
+
"extra_key": "value",
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
with pytest.raises(Exception, match="validation error"):
|
|
123
|
+
CsvConfig(csv_config_data)
|
|
124
|
+
|
|
125
|
+
csv_config_data["data_columns"] = {
|
|
126
|
+
1: {
|
|
127
|
+
"name": "channel",
|
|
128
|
+
"data_type": "CHANNEL_DATA_TYPE_BIT_FIELD",
|
|
129
|
+
"bit_field_elements": [
|
|
130
|
+
{"index": 1, "name": "bit_field_name_1", "bit_count": 4},
|
|
131
|
+
],
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
CsvConfig(csv_config_data)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_time_column(csv_config_data: dict):
|
|
138
|
+
csv_config_data["time_column"] = {
|
|
139
|
+
"format": "INVALID_TIME_FORMAT",
|
|
140
|
+
"column_number": 1,
|
|
141
|
+
}
|
|
142
|
+
with pytest.raises(Exception, match="Invalid time format"):
|
|
143
|
+
CsvConfig(csv_config_data)
|
|
144
|
+
|
|
145
|
+
csv_config_data["time_column"] = {
|
|
146
|
+
"format": "TIME_FORMAT_RELATIVE_SECONDS",
|
|
147
|
+
"column_number": 1,
|
|
148
|
+
}
|
|
149
|
+
with pytest.raises(Exception, match="Missing 'relative_start_time'"):
|
|
150
|
+
CsvConfig(csv_config_data)
|
|
151
|
+
|
|
152
|
+
csv_config_data["time_column"] = {
|
|
153
|
+
"format": "TIME_FORMAT_ABSOLUTE_UNIX_SECONDS",
|
|
154
|
+
"column_number": 1,
|
|
155
|
+
"relative_start_time": "100",
|
|
156
|
+
}
|
|
157
|
+
with pytest.raises(
|
|
158
|
+
Exception, match="'relative_start_time' specified for non relative time format."
|
|
159
|
+
):
|
|
160
|
+
CsvConfig(csv_config_data)
|
|
161
|
+
|
|
162
|
+
csv_config_data["time_column"] = {
|
|
163
|
+
"format": TimeFormatType.ABSOLUTE_DATETIME,
|
|
164
|
+
"column_number": 1,
|
|
165
|
+
}
|
|
166
|
+
CsvConfig(csv_config_data)
|