Skip to content
Open
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.43.0] - 2026-04-22
### Changed
- Standardised case management return types to return `dict` instead of typed objects, consistent with the rest of the SDK
- `get_case()` now returns `dict[str, Any]` instead of `Case`
- `get_cases()` (batch) now returns `dict[str, Any]` instead of `CaseList`
- `patch_case()` now returns `dict[str, Any]` instead of `Case`

### Removed
- `Case`, `CaseList`, and `SoarPlatformInfo` model classes.

## [0.42.0] - 2026-04-15
### Added
- `fetch_parser_candidates()` method to retrieve parser candidates for a given log type
Expand Down
44 changes: 13 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1376,19 +1376,19 @@ case_ids = {alert.get('caseName') for alert in alert_list if alert.get('caseName

# Get case details using the batch API
if case_ids:
cases = chronicle.get_cases(list(case_ids))
result = chronicle.get_cases(list(case_ids))

# Process cases
for case in cases.cases:
print(f"Case: {case.display_name}")
print(f"Priority: {case.priority}")
print(f"Status: {case.status}")
print(f"Stage: {case.stage}")
for case in result.get("cases", []):
print(f"Case: {case['displayName']}")
print(f"Priority: {case['priority']}")
print(f"Status: {case['status']}")
print(f"Stage: {case['stage']}")

# Access SOAR platform information if available
if case.soar_platform_info:
print(f"SOAR Case ID: {case.soar_platform_info.case_id}")
print(f"SOAR Platform: {case.soar_platform_info.platform_type}")
if case.get("soarPlatformInfo"):
print(f"SOAR Case ID: {case['soarPlatformInfo']['caseId']}")
print(f"SOAR Platform: {case['soarPlatformInfo']['responsePlatformType']}")
```

The alerts response includes:
Expand All @@ -1404,24 +1404,6 @@ You can filter alerts using the snapshot query parameter with fields like:
- `feedback_summary.priority`
- `feedback_summary.status`

### Case Management Helpers

The `CaseList` class provides helper methods for working with cases:

```python
# Get details for specific cases (uses the batch API)
cases = chronicle.get_cases(["case-id-1", "case-id-2"])

# Filter cases by priority
high_priority = cases.filter_by_priority("PRIORITY_HIGH")

# Filter cases by status
open_cases = cases.filter_by_status("STATUS_OPEN")

# Look up a specific case
case = cases.get_case("case-id-1")
```

> **Note**: The case management API uses the `legacy:legacyBatchGetCases` endpoint to retrieve multiple cases in a single request. You can retrieve up to 1000 cases in a single batch.

### Case Management
Expand Down Expand Up @@ -1459,10 +1441,10 @@ Retrieve detailed information about a specific case:
```python
# Get case by ID
case = chronicle.get_case("12345")
print(f"Case: {case.display_name}")
print(f"Priority: {case.priority}")
print(f"Status: {case.status}")
print(f"Stage: {case.stage}")
print(f"Case: {case['displayName']}")
print(f"Priority: {case['priority']}")
print(f"Status: {case['status']}")
print(f"Stage: {case['stage']}")

# Get case with expanded fields
case_expanded = chronicle.get_case("12345", expand="tags,products")
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "secops"
version = "0.42.0"
version = "0.43.0"
description = "Python SDK for wrapping the Google SecOps API for common use cases"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
6 changes: 0 additions & 6 deletions src/secops/chronicle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,6 @@
AdvancedConfig,
AlertCount,
AlertState,
Case,
CaseList,
DailyScheduleDetails,
DataExport,
DataExportStage,
Expand All @@ -141,7 +139,6 @@
PrevalenceData,
PythonVersion,
ScheduleType,
SoarPlatformInfo,
TargetMode,
TileType,
TimeInterval,
Expand Down Expand Up @@ -364,8 +361,6 @@
"AdvancedConfig",
"AlertCount",
"AlertState",
"Case",
"CaseList",
"DailyScheduleDetails",
"Date",
"DayOfWeek",
Expand All @@ -379,7 +374,6 @@
"OneTimeScheduleDetails",
"PrevalenceData",
"ScheduleType",
"SoarPlatformInfo",
"TimeInterval",
"Timeline",
"TimelineBucket",
Expand Down
33 changes: 11 additions & 22 deletions src/secops/chronicle/case.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@

from secops.chronicle.models import (
APIVersion,
Case,
CaseCloseReason,
CaseList,
CasePriority,
)
from secops.chronicle.utils.format_utils import (
Expand Down Expand Up @@ -95,15 +93,15 @@ def get_cases(
)


def get_cases_from_list(client, case_ids: list[str]) -> CaseList:
def get_cases_from_list(client, case_ids: list[str]) -> dict[str, Any]:
"""Get cases from Chronicle.

Args:
client: ChronicleClient instance
case_ids: List of case IDs to retrieve

Returns:
CaseList object with case details
Dictionary containing cases data

Raises:
APIError: If the API request fails
Expand All @@ -112,7 +110,7 @@ def get_cases_from_list(client, case_ids: list[str]) -> CaseList:
if len(case_ids) > 1000:
raise ValueError("Maximum of 1000 cases can be retrieved in a batch")

data = chronicle_request(
return chronicle_request(
client,
method="GET",
endpoint_path="legacy:legacyBatchGetCases",
Expand All @@ -121,13 +119,6 @@ def get_cases_from_list(client, case_ids: list[str]) -> CaseList:
error_message="Failed to get cases",
)

cases = []
if "cases" in data:
for case_data in data["cases"]:
cases.append(Case.from_dict(case_data))

return CaseList(cases)


def execute_bulk_add_tag(
client, case_ids: list[int], tags: list[str]
Expand Down Expand Up @@ -345,7 +336,9 @@ def execute_bulk_reopen(
)


def get_case(client, case_name: str, expand: str | None = None) -> Case:
def get_case(
client, case_name: str, expand: str | None = None
) -> dict[str, Any]:
"""Get a single case details.

Args:
Expand All @@ -357,7 +350,7 @@ def get_case(client, case_name: str, expand: str | None = None) -> Case:
expand: Optional expand field for getting related resources

Returns:
Case object with case details
Dictionary containing case details

Raises:
APIError: If the API request fails
Expand All @@ -370,7 +363,7 @@ def get_case(client, case_name: str, expand: str | None = None) -> Case:
}
)

data = chronicle_request(
return chronicle_request(
client,
method="GET",
endpoint_path=f"cases/{endpoint_path}",
Expand All @@ -379,8 +372,6 @@ def get_case(client, case_name: str, expand: str | None = None) -> Case:
error_message="Failed to get case",
)

return Case.from_dict(data)


def list_cases(
client,
Expand Down Expand Up @@ -478,7 +469,7 @@ def patch_case(
case_name: str,
case_data: dict[str, Any],
update_mask: str | None = None,
) -> Case:
) -> dict[str, Any]:
"""Update a case using partial update (PATCH).

Args:
Expand All @@ -491,7 +482,7 @@ def patch_case(
update_mask: Optional comma-separated list of fields to update

Returns:
Updated Case object
Dictionary containing the updated case

Raises:
APIError: If the API request fails
Expand Down Expand Up @@ -519,7 +510,7 @@ def patch_case(
}
)

data = chronicle_request(
return chronicle_request(
client,
method="PATCH",
endpoint_path=f"cases/{endpoint_path}",
Expand All @@ -528,5 +519,3 @@ def patch_case(
params=params or None,
error_message="Failed to patch case",
)

return Case.from_dict(data)
15 changes: 8 additions & 7 deletions src/secops/chronicle/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,6 @@
APIVersion,
AlertState,
CaseCloseReason,
CaseList,
CasePriority,
DashboardChart,
DashboardQuery,
Expand Down Expand Up @@ -1142,7 +1141,7 @@ def list_iocs(
prioritized_only,
)

def get_cases(self, case_ids: list[str]) -> CaseList:
def get_cases(self, case_ids: list[str]) -> dict[str, Any]:
"""Get case information for the specified case IDs.

Uses the legacy:legacyBatchGetCases endpoint to retrieve multiple cases
Expand All @@ -1152,23 +1151,25 @@ def get_cases(self, case_ids: list[str]) -> CaseList:
case_ids: List of case IDs to retrieve (maximum 1000)

Returns:
A CaseList object containing the requested cases
Dictionary containing cases data

Raises:
APIError: If the API request fails
ValueError: If more than 1000 case IDs are provided
"""
return get_cases_from_list(self, case_ids)

def get_case(self, case_name: str, expand: str | None = None) -> "Case":
def get_case(
self, case_name: str, expand: str | None = None
) -> dict[str, Any]:
"""Get a single case details.

Args:
case_name: Case resource name or case ID.
expand: Optional expand field for getting related resources

Returns:
Case object with case details
Dictionary containing case details

Raises:
APIError: If the API request fails
Expand Down Expand Up @@ -1224,7 +1225,7 @@ def patch_case(
case_name: str,
case_data: dict[str, Any],
update_mask: str | None = None,
) -> "Case":
) -> dict[str, Any]:
"""Update a case using partial update (PATCH).

Args:
Expand All @@ -1233,7 +1234,7 @@ def patch_case(
update_mask: Optional comma-separated list of fields to update

Returns:
Updated Case object
Dictionary containing the updated case

Raises:
APIError: If the API request fails
Expand Down
Loading