Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from sift_client._internal.low_level_wrappers.channels import ChannelsLowLevelClient
from sift_client._internal.low_level_wrappers.ingestion import IngestionLowLevelClient
from sift_client._internal.low_level_wrappers.ping import PingLowLevelClient
from sift_client._internal.low_level_wrappers.reports import ReportsLowLevelClient
from sift_client._internal.low_level_wrappers.rules import RulesLowLevelClient
from sift_client._internal.low_level_wrappers.runs import RunsLowLevelClient

Expand All @@ -14,6 +15,7 @@
"ChannelsLowLevelClient",
"IngestionLowLevelClient",
"PingLowLevelClient",
"ReportsLowLevelClient",
"RulesLowLevelClient",
"RunsLowLevelClient",
]
268 changes: 268 additions & 0 deletions python/lib/sift_client/_internal/low_level_wrappers/reports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any, cast

from sift.reports.v1.reports_pb2 import (
CancelReportRequest,
CreateReportFromReportTemplateRequest,
CreateReportFromRulesRequest,
CreateReportRequest,
CreateReportRequestClientKeys,
CreateReportRequestRuleIds,
CreateReportResponse,
GetReportRequest,
GetReportResponse,
ListReportsRequest,
ListReportsResponse,
RerunReportRequest,
RerunReportResponse,
)
from sift.reports.v1.reports_pb2_grpc import ReportServiceStub

from sift_client._internal.low_level_wrappers.base import LowLevelClientBase
from sift_client.sift_types.report import Report
from sift_client.transport import WithGrpcClient

if TYPE_CHECKING:
from sift_client.transport.grpc_transport import GrpcClient

# Configure logging
logger = logging.getLogger(__name__)


class ReportsLowLevelClient(LowLevelClientBase, WithGrpcClient):
"""Low-level client for the ReportsAPI.

This class provides a thin wrapper around the autogenerated bindings for the ReportsAPI.
"""

def __init__(self, grpc_client: GrpcClient):
"""Initialize the ReportsLowLevelClient.

Args:
grpc_client: The gRPC client to use for making API calls.
"""
super().__init__(grpc_client)

async def get_report(self, report_id: str) -> Report:
"""Get a report by report_id.

Args:
report_id: The report ID to get.

Returns:
The Report.

Raises:
ValueError: If report_id is not provided.
"""
if not report_id:
raise ValueError("report_id must be provided")

request = GetReportRequest(report_id=report_id)
response = await self._grpc_client.get_stub(ReportServiceStub).GetReport(request)
grpc_report = cast("GetReportResponse", response).report
return Report._from_proto(grpc_report)

async def list_reports(
self,
*,
page_size: int | None = None,
page_token: str | None = None,
query_filter: str | None = None,
organization_id: str | None = None,
order_by: str | None = None,
) -> tuple[list[Report], str]:
"""List reports with optional filtering and pagination.

Args:
page_size: The maximum number of reports to return.
page_token: A page token for pagination.
query_filter: A CEL filter string.
organization_id: The organization ID to filter by.
order_by: How to order the retrieved reports.

Returns:
A tuple of (reports, next_page_token).
"""
request_kwargs: dict[str, Any] = {}
if page_size is not None:
request_kwargs["page_size"] = page_size
if page_token is not None:
request_kwargs["page_token"] = page_token
if query_filter is not None:
request_kwargs["filter"] = query_filter
if organization_id is not None:
request_kwargs["organization_id"] = organization_id
if order_by is not None:
request_kwargs["order_by"] = order_by

request = ListReportsRequest(**request_kwargs)
response = await self._grpc_client.get_stub(ReportServiceStub).ListReports(request)
response = cast("ListReportsResponse", response)

reports = [Report._from_proto(report) for report in response.reports]
return reports, response.next_page_token

async def list_all_reports(
self,
*,
query_filter: str | None = None,
organization_id: str | None = None,
order_by: str | None = None,
max_results: int | None = None,
) -> list[Report]:
"""List all reports with optional filtering.

Args:
query_filter: A CEL filter string.
organization_id: The organization ID to filter by.
order_by: How to order the retrieved reports.
max_results: Maximum number of results to return.

Returns:
A list of all matching reports.
"""
return await self._handle_pagination(
self.list_reports,
kwargs={
"query_filter": query_filter,
"organization_id": organization_id,
},
order_by=order_by,
max_results=max_results,
)

async def create_report_from_template(
self,
*,
report_template_id: str,
run_id: str,
organization_id: str,
name: str | None = None,
) -> Report:
"""Create a new report from a report template.

Args:
report_template_id: The ID of the report template to use.
run_id: The run ID to associate with the report.
organization_id: The organization ID.
name: Optional name for the report.

Returns:
The created Report.
"""
template_request = CreateReportFromReportTemplateRequest(
report_template_id=report_template_id
)

request_kwargs: dict[str, Any] = {
"report_from_report_template_request": template_request,
"organization_id": organization_id,
"run_id": run_id,
}

if name is not None:
request_kwargs["name"] = name

request = CreateReportRequest(**request_kwargs)
response = await self._grpc_client.get_stub(ReportServiceStub).CreateReport(request)
grpc_report = cast("CreateReportResponse", response).report
return Report._from_proto(grpc_report)

async def create_report_from_rules(
self,
*,
name: str,
description: str | None = None,
tag_names: list[str] | None = None,
rule_ids: list[str] | None = None,
rule_client_keys: list[str] | None = None,
run_id: str,
organization_id: str,
) -> Report:
"""Create a new report from rules.

Args:
name: The name of the report.
description: Optional description of the report.
tag_names: List of tag names to associate with the report.
rule_ids: List of rule IDs to include in the report.
rule_client_keys: List of rule client keys to include in the report.
run_id: The run ID to associate with the report.
organization_id: The organization ID.

Returns:
The created Report.

Raises:
ValueError: If neither rule_ids nor rule_client_keys are provided.
"""
if not rule_ids and not rule_client_keys:
raise ValueError("Either rule_ids or rule_client_keys must be provided")

rules_request_kwargs: dict[str, Any] = {
"name": name,
}

if description is not None:
rules_request_kwargs["description"] = description

if tag_names is not None:
rules_request_kwargs["tag_names"] = tag_names

if rule_ids is not None:
rules_request_kwargs["rule_ids"] = CreateReportRequestRuleIds(rule_ids=rule_ids)
elif rule_client_keys is not None:
rules_request_kwargs["rule_client_keys"] = CreateReportRequestClientKeys(
rule_client_keys=rule_client_keys
)

rules_request = CreateReportFromRulesRequest(**rules_request_kwargs)

request_kwargs: dict[str, Any] = {
"report_from_rules_request": rules_request,
"organization_id": organization_id,
"run_id": run_id,
}

request = CreateReportRequest(**request_kwargs)
response = await self._grpc_client.get_stub(ReportServiceStub).CreateReport(request)
grpc_report = cast("CreateReportResponse", response).report
return Report._from_proto(grpc_report)

async def rerun_report(self, report_id: str) -> tuple[str, str]:
"""Rerun a report.

Args:
report_id: The ID of the report to rerun.

Returns:
A tuple of (job_id, new_report_id).

Raises:
ValueError: If report_id is not provided.
"""
if not report_id:
raise ValueError("report_id must be provided")

request = RerunReportRequest(report_id=report_id)
response = await self._grpc_client.get_stub(ReportServiceStub).RerunReport(request)
response = cast("RerunReportResponse", response)
return response.job_id, response.report_id

async def cancel_report(self, report_id: str) -> None:
"""Cancel a report.

Args:
report_id: The ID of the report to cancel.

Raises:
ValueError: If report_id is not provided.
"""
if not report_id:
raise ValueError("report_id must be provided")

request = CancelReportRequest(report_id=report_id)
await self._grpc_client.get_stub(ReportServiceStub).CancelReport(request)
85 changes: 85 additions & 0 deletions python/lib/sift_client/_internal/low_level_wrappers/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
import logging
from typing import TYPE_CHECKING, Any, cast

from sift.common.type.v1.resource_identifier_pb2 import ResourceIdentifier
from sift.rule_evaluation.v1.rule_evaluation_pb2 import (
EvaluateRulesRequest,
EvaluateRulesResponse,
RunTimeRange,
)
from sift.rule_evaluation.v1.rule_evaluation_pb2_grpc import RuleEvaluationServiceStub
from sift.rules.v1.rules_pb2 import (
BatchDeleteRulesRequest,
BatchGetRulesRequest,
Expand Down Expand Up @@ -31,15 +38,20 @@
from sift.rules.v1.rules_pb2_grpc import RuleServiceStub

from sift_client._internal.low_level_wrappers.base import LowLevelClientBase
from sift_client._internal.low_level_wrappers.reports import ReportsLowLevelClient
from sift_client.sift_types.rule import (
Rule,
RuleAction,
RuleUpdate,
)
from sift_client.transport import GrpcClient, WithGrpcClient
from sift_client.util.util import count_non_none

if TYPE_CHECKING:
from datetime import datetime

from sift_client.sift_types.channel import ChannelReference
from sift_client.sift_types.report import Report

# Configure logging
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -445,3 +457,76 @@ async def list_all_rules(
order_by=order_by,
max_results=max_results,
)

async def evaluate_rules(
self,
*,
run_id: str | None = None,
assets: list[str] | None = None,
all_applicable_rules: bool | None = None,
run_start_time: datetime | None = None,
run_end_time: datetime | None = None,
rule_ids: list[str] | None = None,
rule_version_ids: list[str] | None = None,
report_template_id: str | None = None,
tags: list[str] | None = None,
) -> Report | None:
"""Evaluate a rule.

Args:
run_id: The run ID to evaluate.
assets: The assets to evaluate.
run_start_time: The start time of the run.
run_end_time: The end time of the run.
all_applicable_rules: Whether to evaluate all rules applicable to the selected run, assets, or time range.
rule_ids: The rule IDs to evaluate.
rule_version_ids: The rule version IDs to evaluate.
report_template_id: The report template ID to evaluate.
tags: Optional tags to add to generated annotations.

Returns:
The result of the rule execution.
"""
if count_non_none(run_id, assets, run_start_time, run_end_time) > 1:
raise ValueError(
"Pick only one run_id, assets, or (run_start_time and run_end_time) to select what to evaluate against."
)

all_applicable_rules = (
None if not all_applicable_rules else True
) # Cast to None if False so we don't count it against other filters if they aren't opting in.
if count_non_none(rule_ids, rule_version_ids, report_template_id, all_applicable_rules) > 1:
raise ValueError(
"Pick only one rule_ids, rule_version_ids, report_template_id, or all_applicable_rules to further filter which rules to evaluate."
)

kwargs: dict[str, Any] = {}
if run_start_time and run_end_time:
kwargs["run_time_range"] = RunTimeRange(
run=run_id, start_time=run_start_time, end_time=run_end_time
)
if run_id:
kwargs["run"] = ResourceIdentifier(id=run_id)
if assets:
kwargs["assets"] = assets
if all_applicable_rules:
kwargs["all_applicable_rules"] = all_applicable_rules
if rule_ids:
kwargs["rules"] = rule_ids
if rule_version_ids:
kwargs["rule_versions"] = rule_version_ids
if report_template_id:
kwargs["report_template"] = report_template_id
if tags:
kwargs["tags"] = tags

request = EvaluateRulesRequest(**kwargs)
response = await self._grpc_client.get_stub(RuleEvaluationServiceStub).EvaluateRules(
request
)
response = cast("EvaluateRulesResponse", response)
report_id = response.report_id
if report_id:
report = await ReportsLowLevelClient(self._grpc_client).get_report(report_id=report_id)
return report
return None
Loading
Loading