From 50d6c758471ff67805ae09c287305491023aa3b7 Mon Sep 17 00:00:00 2001 From: Marc Julien Date: Mon, 8 Dec 2025 16:51:19 -0800 Subject: [PATCH 1/3] python(feat): Add support for is_live to rules in sift_py --- .../ingestion_with_python_config/telemetry_config.py | 3 +-- .../ingestion_with_yaml_config/rule_modules/nostromo.yml | 2 ++ .../ingestion_with_yaml_config/rule_modules/velocity.yml | 2 ++ .../ingestion_with_yaml_config/rule_modules/voltage.yml | 1 + python/lib/sift_py/rule/config.py | 6 ++++++ python/lib/sift_py/rule/service.py | 4 ++++ python/lib/sift_py/yaml/rule.py | 5 +++++ 7 files changed, 21 insertions(+), 2 deletions(-) diff --git a/python/examples/ingestion_with_python_config/telemetry_config.py b/python/examples/ingestion_with_python_config/telemetry_config.py index 1c4a3904e..785bb84c2 100644 --- a/python/examples/ingestion_with_python_config/telemetry_config.py +++ b/python/examples/ingestion_with_python_config/telemetry_config.py @@ -12,8 +12,6 @@ RuleConfig, ) -RULE_NAMESPACES_DIR = Path().joinpath("rule_modules") - def nostromos_lv_426() -> TelemetryConfig: log_channel = ChannelConfig( @@ -85,6 +83,7 @@ def nostromos_lv_426() -> TelemetryConfig: FlowConfig(name="logs", channels=[log_channel]), ], rules=[ + # Add `is_live=True` if you want these rules to run on live data. RuleConfig( name="overheating", description="Checks for vehicle overheating", diff --git a/python/examples/ingestion_with_yaml_config/rule_modules/nostromo.yml b/python/examples/ingestion_with_yaml_config/rule_modules/nostromo.yml index 0ad84e144..30b0468f7 100644 --- a/python/examples/ingestion_with_yaml_config/rule_modules/nostromo.yml +++ b/python/examples/ingestion_with_yaml_config/rule_modules/nostromo.yml @@ -1,3 +1,5 @@ +# Example rules configs. Add 'is_live' if you want these rules to run on live data. + rules: - name: overheating description: Checks for vehicle overheating diff --git a/python/examples/ingestion_with_yaml_config/rule_modules/velocity.yml b/python/examples/ingestion_with_yaml_config/rule_modules/velocity.yml index 968c8132c..e0139af9c 100644 --- a/python/examples/ingestion_with_yaml_config/rule_modules/velocity.yml +++ b/python/examples/ingestion_with_yaml_config/rule_modules/velocity.yml @@ -1,3 +1,5 @@ +# Example rules configs. Add 'is_live: true' if you want these rules to run on live data. + rules: - name: vehicle_stuck description: Triggers if the vehicle velocity is not 0 for 5s after entering accelerating state diff --git a/python/examples/ingestion_with_yaml_config/rule_modules/voltage.yml b/python/examples/ingestion_with_yaml_config/rule_modules/voltage.yml index dd9da4d4f..6d8ce5e58 100644 --- a/python/examples/ingestion_with_yaml_config/rule_modules/voltage.yml +++ b/python/examples/ingestion_with_yaml_config/rule_modules/voltage.yml @@ -1,3 +1,4 @@ +# Example rules configs. Add 'is_live: true' if you want these rules to run on live data. rules: - name: overvoltage description: Checks for overvoltage while accelerating diff --git a/python/lib/sift_py/rule/config.py b/python/lib/sift_py/rule/config.py index 3b38e3f48..fbba4abcc 100644 --- a/python/lib/sift_py/rule/config.py +++ b/python/lib/sift_py/rule/config.py @@ -27,6 +27,8 @@ class RuleConfig(AsJson): - `tag_names`: A list of asset names that this rule should be applied to. ONLY VALID if defining rules outside of a telemetry config. - `contextual_channels`: A list of channel names that provide context but aren't directly used in the expression. - `is_external`: If this is an external rule. + - `is_live`: If set to True then this rule will be evaluated on live data, otherwise live rule evaluation will be disabled. + This rule can still be used, however, in report generation. """ name: str @@ -38,6 +40,7 @@ class RuleConfig(AsJson): asset_names: List[str] contextual_channels: List[str] is_external: bool + is_live: bool _rule_id: Optional[str] # Allow passing of rule_id when existing config retrieved from API def __init__( @@ -55,6 +58,7 @@ def __init__( sub_expressions: Dict[str, Any] = {}, contextual_channels: Optional[List[str]] = None, is_external: bool = False, + is_live: bool = False, ): self.channel_references = _channel_references_from_dicts(channel_references) self.contextual_channels = contextual_channels or [] @@ -66,6 +70,7 @@ def __init__( self.description = description self.expression = self.__class__.interpolate_sub_expressions(expression, sub_expressions) self.is_external = is_external + self.is_live = is_live self._rule_id = None def as_json(self) -> Any: @@ -83,6 +88,7 @@ def as_json(self) -> Any: "description": self.description, "expression": self.expression, "is_external": self.is_external, + "is_live": self.is_live, } hash_map["expression_channel_references"] = self.channel_references diff --git a/python/lib/sift_py/rule/service.py b/python/lib/sift_py/rule/service.py index 1b7f0f425..db2951725 100644 --- a/python/lib/sift_py/rule/service.py +++ b/python/lib/sift_py/rule/service.py @@ -98,6 +98,7 @@ def create_external_rules_from_yaml( """ Creates external rules from a YAML spec in the Sift API. For more on rule YAML definitions, see `sift_py.ingestion.config.yaml.spec.RuleYamlSpec`. + If is_external is set in the YAML, this overrides that. Args: paths: The list of YAML paths to load. @@ -229,6 +230,8 @@ def _parse_rules_from_yaml( contextual_channels=contextual_channels, asset_names=rule_yaml.get("asset_names", []), sub_expressions=subexpr, + is_external=rule_yaml.get("is_external", False), + is_live=rule_yaml.get("is_live", False), ) ) @@ -510,6 +513,7 @@ def _update_req_from_rule_config( ), contextual_channels=ContextualChannels(channels=contextual_channel_names), is_external=config.is_external, + is_live_evaluation_enabled=config.is_live, ) def get_rule(self, rule: str) -> Optional[RuleConfig]: diff --git a/python/lib/sift_py/yaml/rule.py b/python/lib/sift_py/yaml/rule.py index abb05d965..aec8cad18 100644 --- a/python/lib/sift_py/yaml/rule.py +++ b/python/lib/sift_py/yaml/rule.py @@ -270,6 +270,9 @@ class RuleYamlSpec(TypedDict): `sub_expressions`: A list of sub-expressions which is a mapping of place-holders to sub-expressions. Only used if using named expressions. `asset_names`: A list of asset names that this rule should be applied to. ONLY VALID if defining rules outside of a telemetry config. `tag_names`: A list of tag names that this rule should be applied to. ONLY VALID if defining rules outside of a telemetry config. + `is_external`: If this is an external rule. + `is_live`: If set to True then this rule will be evaluated on live data, otherwise live rule evaluation will be disabled. + This rule can still be used, however, in report generation. Channel references: A channel reference is a string containing a numerical value prefixed with "$". Examples include "$1", "$2", "$11", and so on. @@ -326,6 +329,8 @@ class RuleYamlSpec(TypedDict): sub_expressions: NotRequired[List[Dict[str, str]]] asset_names: NotRequired[List[str]] tag_names: NotRequired[List[str]] + is_external: NotRequired[bool] + is_live: NotRequired[bool] class NamedExpressionYamlSpec(TypedDict): From 024ebd4ef6b9babc61ec92b5199bd50ed1bc356c Mon Sep 17 00:00:00 2001 From: Marc Julien Date: Tue, 9 Dec 2025 11:24:28 -0800 Subject: [PATCH 2/3] Lint --- python/lib/sift_py/rule/config.py | 2 +- python/lib/sift_py/yaml/rule.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/lib/sift_py/rule/config.py b/python/lib/sift_py/rule/config.py index fbba4abcc..748405d4f 100644 --- a/python/lib/sift_py/rule/config.py +++ b/python/lib/sift_py/rule/config.py @@ -27,7 +27,7 @@ class RuleConfig(AsJson): - `tag_names`: A list of asset names that this rule should be applied to. ONLY VALID if defining rules outside of a telemetry config. - `contextual_channels`: A list of channel names that provide context but aren't directly used in the expression. - `is_external`: If this is an external rule. - - `is_live`: If set to True then this rule will be evaluated on live data, otherwise live rule evaluation will be disabled. + - `is_live`: If set to True then this rule will be evaluated on live data, otherwise live rule evaluation will be disabled. This rule can still be used, however, in report generation. """ diff --git a/python/lib/sift_py/yaml/rule.py b/python/lib/sift_py/yaml/rule.py index aec8cad18..e2220eb9b 100644 --- a/python/lib/sift_py/yaml/rule.py +++ b/python/lib/sift_py/yaml/rule.py @@ -271,7 +271,7 @@ class RuleYamlSpec(TypedDict): `asset_names`: A list of asset names that this rule should be applied to. ONLY VALID if defining rules outside of a telemetry config. `tag_names`: A list of tag names that this rule should be applied to. ONLY VALID if defining rules outside of a telemetry config. `is_external`: If this is an external rule. - `is_live`: If set to True then this rule will be evaluated on live data, otherwise live rule evaluation will be disabled. + `is_live`: If set to True then this rule will be evaluated on live data, otherwise live rule evaluation will be disabled. This rule can still be used, however, in report generation. Channel references: From 676006e99ef749f1d9ecef13a097f549cf7b9393 Mon Sep 17 00:00:00 2001 From: Marc Julien Date: Tue, 9 Dec 2025 12:45:11 -0800 Subject: [PATCH 3/3] Lint --- .../examples/ingestion_with_python_config/telemetry_config.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/python/examples/ingestion_with_python_config/telemetry_config.py b/python/examples/ingestion_with_python_config/telemetry_config.py index 785bb84c2..62c16b816 100644 --- a/python/examples/ingestion_with_python_config/telemetry_config.py +++ b/python/examples/ingestion_with_python_config/telemetry_config.py @@ -1,5 +1,3 @@ -from pathlib import Path - from sift_py.ingestion.channel import ( ChannelBitFieldElement, ChannelConfig,