Skip to content
Merged
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,8 +5,10 @@
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
from sift_client._internal.low_level_wrappers.tags import TagsLowLevelClient
from sift_client._internal.low_level_wrappers.test_results import TestResultsLowLevelClient
from sift_client._internal.low_level_wrappers.upload import UploadLowLevelClient

Expand All @@ -16,8 +18,10 @@
"ChannelsLowLevelClient",
"IngestionLowLevelClient",
"PingLowLevelClient",
"ReportsLowLevelClient",
"RulesLowLevelClient",
"RunsLowLevelClient",
"TagsLowLevelClient",
"TestResultsLowLevelClient",
"UploadLowLevelClient",
]
179 changes: 179 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,179 @@
from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any, cast

from sift.reports.v1.reports_pb2 import (
CancelReportRequest,
GetReportRequest,
GetReportResponse,
ListReportsRequest,
ListReportsResponse,
RerunReportRequest,
RerunReportResponse,
UpdateReportRequest,
)
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, ReportUpdate
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 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)

async def update_report(self, update: ReportUpdate) -> Report:
"""Update a report.

Args:
update: The updates to apply.

Returns:
The updated report.
"""
report_proto, field_mask = update.to_proto_with_mask()
request = UpdateReportRequest(report=report_proto, update_mask=field_mask)
await self._grpc_client.get_stub(ReportServiceStub).UpdateReport(request)
# Unfortunately, updating a report doesn't return the updated report.
return await self.get_report(update.resource_id)
105 changes: 105 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,14 @@
import logging
from typing import TYPE_CHECKING, Any, cast

from sift.common.type.v1.resource_identifier_pb2 import ResourceIdentifier, ResourceIdentifiers
from sift.rule_evaluation.v1.rule_evaluation_pb2 import (
AssetsTimeRange,
EvaluateRulesRequest,
EvaluateRulesResponse,
RunTimeRange,
)
from sift.rule_evaluation.v1.rule_evaluation_pb2_grpc import RuleEvaluationServiceStub
from sift.rules.v1.rules_pb2 import (
ArchiveRuleRequest,
BatchArchiveRulesRequest,
Expand Down Expand Up @@ -30,15 +38,22 @@
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._internal.util.timestamp import to_pb_timestamp
from sift_client._internal.util.util import count_non_none
from sift_client.sift_types.rule import (
Rule,
RuleCreate,
RuleUpdate,
)
from sift_client.sift_types.tag import Tag
from sift_client.transport import GrpcClient, WithGrpcClient

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 @@ -437,3 +452,93 @@ async def list_all_rules(
order_by=order_by,
max_results=max_results,
)

async def evaluate_rules(
self,
*,
run_id: str | None = None,
asset_ids: list[str] | None = None,
all_applicable_rules: bool | None = None,
start_time: datetime | None = None,
end_time: datetime | None = None,
rule_ids: list[str] | None = None,
rule_version_ids: list[str] | None = None,
report_template_id: str | None = None,
report_name: str | None = None,
tags: list[str | Tag] | None = None,
organization_id: str | None = None,
) -> tuple[int, Report | None, str | None]:
"""Evaluate a rule.

Args:
run_id: The run ID to evaluate.
asset_ids: The asset IDs to evaluate.
start_time: The start time of the 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.
report_name: The name of the report to create.
tags: Optional tags to add to generated annotations.
organization_id: The organization ID to evaluate.

Returns:
The result of the rule execution.
"""
if count_non_none(run_id, asset_ids) > 1:
raise ValueError(
"Pick only one run_id or asset_ids 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] = {}
# Time frame filters are run(ID), run_time_range(ID + start/end time), or assets(asset_ids + start/end time)
if start_time and end_time:
if run_id:
kwargs["run_time_range"] = RunTimeRange(
run=run_id, # type: ignore
start_time=to_pb_timestamp(start_time),
end_time=to_pb_timestamp(end_time), # type: ignore
)
kwargs["assets"] = AssetsTimeRange(
assets={"ids": {"ids": asset_ids}}, # type: ignore
start_time=to_pb_timestamp(start_time),
end_time=to_pb_timestamp(end_time),
)
elif run_id:
kwargs["run"] = ResourceIdentifier(id=run_id)
if all_applicable_rules:
kwargs["all_applicable_rules"] = all_applicable_rules
if rule_ids:
kwargs["rules"] = {"rules": ResourceIdentifiers(ids={"ids": rule_ids})} # type: ignore
if rule_version_ids:
kwargs["rule_versions"] = rule_version_ids
if report_template_id:
kwargs["report_template"] = report_template_id
if tags:
kwargs["tags"] = [tag.name if isinstance(tag, Tag) else tag for tag in tags]
if report_name:
kwargs["report_name"] = report_name
if organization_id:
kwargs["organization_id"] = organization_id

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