Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
135 commits
Select commit Hold shift + click to select a range
6658b4b
Added remote files to python client
andrew-siftstack Nov 5, 2025
127fdbf
Clean up files
andrew-siftstack Nov 5, 2025
fcd7bdb
clean up
andrew-siftstack Nov 5, 2025
4b88663
format
andrew-siftstack Nov 5, 2025
24040a3
clean up
andrew-siftstack Nov 5, 2025
b43b279
Annotation -> asset
andrew-siftstack Nov 5, 2025
ce28cec
cleanup
andrew-siftstack Nov 5, 2025
b215634
cleanup
andrew-siftstack Nov 5, 2025
28dc8c9
remote files has no high level api and must be accessed via their entity
andrew-siftstack Nov 6, 2025
dde1af3
lint
andrew-siftstack Nov 6, 2025
afb2cd9
clean up
andrew-siftstack Nov 6, 2025
f7a5725
clean up
andrew-siftstack Nov 6, 2025
a77acef
fix import cycle
andrew-siftstack Nov 6, 2025
cc91882
Merge branch 'main' into python/remote_files
andrew-siftstack Nov 6, 2025
669735e
format
andrew-siftstack Nov 6, 2025
4fe0fb9
Merge branch 'python/remote_files' of https://github.com/sift-stack/s…
andrew-siftstack Nov 6, 2025
50df4d0
cleanup
andrew-siftstack Nov 6, 2025
350673a
cleanup
andrew-siftstack Nov 6, 2025
c5561e0
clean up
andrew-siftstack Nov 6, 2025
c575387
cleanup
andrew-siftstack Nov 6, 2025
a13a26f
cleanup
andrew-siftstack Nov 6, 2025
35a63c0
sift client passed through low level remote files
andrew-siftstack Nov 6, 2025
e16d500
clean up
andrew-siftstack Nov 6, 2025
bb88b8d
cleanup
andrew-siftstack Nov 6, 2025
a400416
cleanup
andrew-siftstack Nov 6, 2025
d977c45
cleanup
andrew-siftstack Nov 6, 2025
ebd53d9
cleanup
andrew-siftstack Nov 6, 2025
21c2cd2
cleanup
andrew-siftstack Nov 7, 2025
eae02aa
cleanup
andrew-siftstack Nov 7, 2025
21b464d
cleanup
andrew-siftstack Nov 7, 2025
f997848
cleanup
andrew-siftstack Nov 7, 2025
484bdaa
test fixes
andrew-siftstack Nov 7, 2025
1951c44
test fixes
andrew-siftstack Nov 7, 2025
7cc8f59
test fix
andrew-siftstack Nov 7, 2025
86eb728
test fixes
andrew-siftstack Nov 7, 2025
92f2882
test fixes
andrew-siftstack Nov 7, 2025
e44727a
test fixes
andrew-siftstack Nov 7, 2025
8217025
test fixes
andrew-siftstack Nov 7, 2025
559797a
Luck/python/remote files (#373)
alexluck-sift Nov 10, 2025
dfba223
formatting
andrew-siftstack Nov 10, 2025
616251e
format
andrew-siftstack Nov 10, 2025
53a15a1
cleanup
andrew-siftstack Nov 10, 2025
f7e9017
add file attachments api back in
andrew-siftstack Nov 10, 2025
90afb73
format
andrew-siftstack Nov 10, 2025
d9a46bd
clean up
andrew-siftstack Nov 10, 2025
69dd11d
test fixes
andrew-siftstack Nov 10, 2025
cb9011a
update file attachments
andrew-siftstack Nov 11, 2025
a34af86
Merge branch 'main' into python/remote_files
andrew-siftstack Nov 12, 2025
e0c8075
clean up
andrew-siftstack Nov 12, 2025
bfb3bf3
cleanup
andrew-siftstack Nov 12, 2025
efeef8e
Merge branch 'main' of https://github.com/sift-stack/sift into python…
andrew-siftstack Nov 12, 2025
0c13067
cleanup
andrew-siftstack Nov 12, 2025
66e9a8b
format
andrew-siftstack Nov 12, 2025
5fc3d34
clean up
andrew-siftstack Nov 12, 2025
df19f31
cleanup
andrew-siftstack Nov 12, 2025
97ab2c2
update interface
andrew-siftstack Nov 12, 2025
8c1d9f4
format
andrew-siftstack Nov 12, 2025
b4e3352
cleanup
andrew-siftstack Nov 12, 2025
2eeac8a
cleanup
andrew-siftstack Nov 12, 2025
8cf30a3
cleanup batch delete
andrew-siftstack Nov 12, 2025
6ad71d9
cleanup
andrew-siftstack Nov 12, 2025
df3f77f
cleanup
andrew-siftstack Nov 12, 2025
2d91c10
cleanup
andrew-siftstack Nov 12, 2025
efbbf29
add file uploads
andrew-siftstack Nov 13, 2025
c27086a
ruff check
andrew-siftstack Nov 13, 2025
a6faae6
format
andrew-siftstack Nov 13, 2025
b9cb983
fix types
andrew-siftstack Nov 13, 2025
8f9f612
format
andrew-siftstack Nov 13, 2025
e99170e
small fix
andrew-siftstack Nov 13, 2025
76df1b2
Merge branch 'main' of https://github.com/sift-stack/sift into python…
andrew-siftstack Nov 14, 2025
7f8e2c9
Update sync stubs for file attachments API
andrew-siftstack Nov 14, 2025
a573ae6
cleanup
andrew-siftstack Nov 14, 2025
d3ce6fc
Merge branch 'main' into python/remote_files
andrew-siftstack Nov 14, 2025
bec37f3
cleanup
andrew-siftstack Nov 14, 2025
dce8c41
Add missing _run method to FileAttachmentsAPI stubs
andrew-siftstack Nov 14, 2025
951b240
restore rust files
andrew-siftstack Nov 14, 2025
8fa0b46
remove delete from mixin file attachment
andrew-siftstack Nov 14, 2025
80080e3
format
andrew-siftstack Nov 14, 2025
c65d283
small fix
andrew-siftstack Nov 14, 2025
2b265d1
Update file attachments list_ method signature in stubs
andrew-siftstack Nov 14, 2025
15b3da3
small fix
andrew-siftstack Nov 14, 2025
1e34e7a
Add entity parameter to file attachments list_ method
andrew-siftstack Nov 14, 2025
01866bf
imports fix
andrew-siftstack Nov 14, 2025
7dc5571
Restore test_value.py that was accidentally deleted
andrew-siftstack Nov 14, 2025
164a013
Restore sift_stream_bindings files to match main
andrew-siftstack Nov 14, 2025
7896a2d
don't use Entity
andrew-siftstack Nov 14, 2025
a26b426
add tests and use cel filter
andrew-siftstack Nov 14, 2025
5033bf2
lint fixes
andrew-siftstack Nov 14, 2025
e2aeac8
format
andrew-siftstack Nov 14, 2025
009bb9a
type checking
andrew-siftstack Nov 14, 2025
65dcad7
small fix
andrew-siftstack Nov 14, 2025
8580896
small fix
andrew-siftstack Nov 14, 2025
45e4376
small fix
andrew-siftstack Nov 15, 2025
17514ad
white space
andrew-siftstack Nov 15, 2025
e860741
circular import
andrew-siftstack Nov 15, 2025
3deb30a
small fixes
andrew-siftstack Nov 15, 2025
3886550
test fix
andrew-siftstack Nov 15, 2025
5a5129b
small fixes
andrew-siftstack Nov 15, 2025
599d14b
format
andrew-siftstack Nov 15, 2025
44124f1
update
andrew-siftstack Nov 15, 2025
9135862
fix
andrew-siftstack Nov 15, 2025
cdcad46
remove import
andrew-siftstack Nov 15, 2025
59b1bfe
small fix
andrew-siftstack Nov 15, 2025
f77302e
format
andrew-siftstack Nov 15, 2025
53f9be7
small fixes
andrew-siftstack Nov 15, 2025
0ad15de
fix`
andrew-siftstack Nov 15, 2025
05d5f7b
small fix
andrew-siftstack Nov 15, 2025
b029190
update stubs
andrew-siftstack Nov 17, 2025
1d9380f
test fixes
andrew-siftstack Nov 17, 2025
1f22818
Update tests
andrew-siftstack Nov 17, 2025
fd70963
Update integration tests
andrew-siftstack Nov 17, 2025
792a624
small fixes
andrew-siftstack Nov 18, 2025
7d5633a
small fixes
andrew-siftstack Nov 18, 2025
a0c6b76
format
andrew-siftstack Nov 18, 2025
7ff2187
update stubs
andrew-siftstack Nov 19, 2025
d493fd2
add test for downloading remote file
andrew-siftstack Nov 19, 2025
a333d93
format
andrew-siftstack Nov 19, 2025
aef5177
format
andrew-siftstack Nov 19, 2025
4b2283c
list_ refactor and docs fix (#393)
alexluck-sift Nov 20, 2025
2d28ff7
cleanup
andrew-siftstack Nov 20, 2025
deb5d68
remote_files download, update cel filters
andrew-siftstack Nov 20, 2025
3d5167c
format
andrew-siftstack Nov 20, 2025
16dd146
format
andrew-siftstack Nov 20, 2025
bb9e731
format
andrew-siftstack Nov 20, 2025
d09f6d0
cleanup
andrew-siftstack Nov 20, 2025
bc1179e
test fixes
andrew-siftstack Nov 20, 2025
80eee3f
add order by back in
andrew-siftstack Nov 20, 2025
99fe7c6
only 1 type at a time
andrew-siftstack Nov 20, 2025
cc1e1db
stub fix
andrew-siftstack Nov 20, 2025
f5b0e80
small asyncio change
andrew-siftstack Nov 24, 2025
f971341
update docstrings and include TODOs
andrew-siftstack Nov 25, 2025
ad9cb59
format
andrew-siftstack Nov 25, 2025
de952c1
not implemented error
andrew-siftstack Nov 25, 2025
1421cb7
Merge branch 'main' of https://github.com/sift-stack/sift into python…
andrew-siftstack Nov 25, 2025
cc96bc0
Merge branch 'main' of https://github.com/sift-stack/sift into python…
andrew-siftstack Nov 26, 2025
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.remote_files import RemoteFilesLowLevelClient
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 @@ -18,6 +19,7 @@
"ChannelsLowLevelClient",
"IngestionLowLevelClient",
"PingLowLevelClient",
"RemoteFilesLowLevelClient",
"ReportsLowLevelClient",
"RulesLowLevelClient",
"RunsLowLevelClient",
Expand Down
204 changes: 204 additions & 0 deletions python/lib/sift_client/_internal/low_level_wrappers/remote_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
from __future__ import annotations

import asyncio
from typing import TYPE_CHECKING, Any, cast

import requests
from sift.remote_files.v1.remote_files_pb2 import (
BatchDeleteRemoteFilesRequest,
DeleteRemoteFileRequest,
GetRemoteFileDownloadUrlRequest,
GetRemoteFileRequest,
GetRemoteFileResponse,
ListRemoteFilesRequest,
ListRemoteFilesResponse,
UpdateRemoteFileRequest,
UpdateRemoteFileResponse,
)
Comment thread
ian-sift marked this conversation as resolved.
from sift.remote_files.v1.remote_files_pb2_grpc import RemoteFileServiceStub

from sift_client._internal.low_level_wrappers.base import (
LowLevelClientBase,
)
from sift_client.transport import GrpcClient, WithGrpcClient

if TYPE_CHECKING:
from sift_client.client import SiftClient
from sift_client.sift_types.file_attachment import FileAttachment, FileAttachmentUpdate


class RemoteFilesLowLevelClient(LowLevelClientBase, WithGrpcClient):
"""Low-level client for the RemoteFilesAPI.

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

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

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

async def get_remote_file(
self, remote_file_id: str, sift_client: SiftClient | None = None
) -> FileAttachment:
"""Get a remote file by ID.

Args:
remote_file_id: The ID of the remote file to retrieve.
sift_client: The SiftClient to attach to the returned RemoteFile.

Returns:
The RemoteFile.
"""
from sift_client.sift_types.file_attachment import FileAttachment

request = GetRemoteFileRequest(remote_file_id=remote_file_id)
response = await self._grpc_client.get_stub(RemoteFileServiceStub).GetRemoteFile(request)
grpc_remote_file = cast("GetRemoteFileResponse", response).remote_file
return FileAttachment._from_proto(grpc_remote_file, sift_client)

async def list_all_remote_files(
self,
query_filter: str | None = None,
max_results: int | None = None,
page_size: int | None = None,
order_by: str | None = None,
sift_client: SiftClient | None = None,
Comment thread
alexluck-sift marked this conversation as resolved.
) -> list[FileAttachment]:
"""List all remote files matching the given query.

Args:
query_filter: The CEL query filter.

max_results: The maximum number of results to return.
page_size: The number of results to return per page.
order_by: The field to order by. Not supported by the backend, but it is here for API consistency. TODO: Add to backend
sift_client: The SiftClient to attach to the returned RemoteFiles.

Returns:
A list of RemoteFiles matching the given query.
"""
return await self._handle_pagination(
self.list_remote_files,
kwargs={"query_filter": query_filter, "sift_client": sift_client},
page_size=page_size,
max_results=max_results,
order_by=order_by,
)

async def list_remote_files(
self,
page_size: int | None = None,
page_token: str | None = None,
query_filter: str | None = None,
order_by: str | None = None,
sift_client: SiftClient | None = None,
) -> tuple[list[FileAttachment], str]:
"""List remote files with pagination support.

Args:
page_size: The number of results to return per page.
page_token: The page token for pagination.
query_filter: The CEL query filter.
order_by: The field to order by. Not supported by the backend, but it is here for API consistency. TODO: Add to backend
sift_client: The SiftClient to attach to the returned RemoteFiles.

Returns:
A tuple of (list of RemoteFiles, next_page_token).
"""
from sift_client.sift_types.file_attachment import FileAttachment

if order_by is not None:
raise NotImplementedError

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

request = ListRemoteFilesRequest(**request_kwargs)
response = await self._grpc_client.get_stub(RemoteFileServiceStub).ListRemoteFiles(request)
response = cast("ListRemoteFilesResponse", response)
return [
FileAttachment._from_proto(rf, sift_client) for rf in response.remote_files
], response.next_page_token

async def update_remote_file(
self, update: FileAttachmentUpdate, sift_client: SiftClient | None = None
) -> FileAttachment:
"""Update a remote file.

Args:
update: The FileAttachmentUpdate containing the fields to update.
sift_client: The SiftClient to attach to the returned RemoteFile.

Returns:
The updated RemoteFile.
"""
from sift_client.sift_types.file_attachment import FileAttachment

grpc_remote_file, update_mask = update.to_proto_with_mask()
request = UpdateRemoteFileRequest(remote_file=grpc_remote_file, update_mask=update_mask)
response = await self._grpc_client.get_stub(RemoteFileServiceStub).UpdateRemoteFile(request)
updated_grpc_remote_file = cast("UpdateRemoteFileResponse", response).remote_file
return FileAttachment._from_proto(updated_grpc_remote_file, sift_client)

async def delete_remote_file(self, remote_file_id: str) -> None:
"""Delete a remote file.

Args:
remote_file_id: The ID of the remote file to delete.
"""
request = DeleteRemoteFileRequest(remote_file_id=remote_file_id)
await self._grpc_client.get_stub(RemoteFileServiceStub).DeleteRemoteFile(request)

async def batch_delete_remote_files(self, remote_file_ids: list[str]) -> None:
"""Batch delete remote files.

Args:
remote_file_ids: The IDs of the remote files to delete (up to 1000).
"""
request = BatchDeleteRemoteFilesRequest(remote_file_ids=remote_file_ids)
await self._grpc_client.get_stub(RemoteFileServiceStub).BatchDeleteRemoteFiles(request)

async def get_remote_file_download_url(self, remote_file_id: str) -> str:
"""Get a download URL for a remote file.

Args:
remote_file_id: The ID of the remote file.

Returns:
The download URL for the remote file.
"""
request = GetRemoteFileDownloadUrlRequest(remote_file_id=remote_file_id)
response = await self._grpc_client.get_stub(RemoteFileServiceStub).GetRemoteFileDownloadUrl(
request
)
return response.download_url

async def download_remote_file(self, file_attachment: FileAttachment) -> bytes:
"""Download a remote file.

Args:
file_attachment: The FileAttachment to download.

Returns:
The downloaded file.
"""
url = await self.get_remote_file_download_url(file_attachment._id_or_error)

# Run the synchronous requests.get in a thread pool to avoid blocking
def _download():
response = requests.get(url)
response.raise_for_status()
return response.content

# Use run_in_executor for Python 3.8 compatibility (asyncio.to_thread was added in 3.9)
loop = asyncio.get_event_loop()
return await loop.run_in_executor(None, _download)
13 changes: 13 additions & 0 deletions python/lib/sift_client/_tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Sift Python

## Running Integration Tests Locally

1. Create Environmental Variables
a. Create or open a .env file in /python
b. Add an API key for SIFT_API_KEY
2. Start Local Sift
3. Asset Data: NostromoLV426
a. Ensure that your local Sift instance contains data for the asset NostromoLV426
b. If it doesn't them export data for NostromoLV426 from development
4. Run tests
a. Run tests using /python/scripts/dev {test, test-integration, test-all}
5 changes: 4 additions & 1 deletion python/lib/sift_client/_tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ def sift_client() -> SiftClient:
grpc_url = os.getenv("SIFT_GRPC_URI", "localhost:50051")
rest_url = os.getenv("SIFT_REST_URI", "localhost:8080")
api_key = os.getenv("SIFT_API_KEY", "")
# If the URL contains localhost, don't use SSL. Most likely running tests or local development.
use_ssl = not ("localhost" in grpc_url or "localhost" in rest_url)

client = SiftClient(
connection_config=SiftConnectionConfig(
api_key=api_key,
grpc_url=grpc_url,
rest_url=rest_url,
use_ssl=True,
use_ssl=use_ssl,
)
)

Expand All @@ -45,6 +47,7 @@ def mock_client():
client.rules = MagicMock()
client.tags = MagicMock()
client.test_results = MagicMock()
client.file_attachments = MagicMock()
client.async_ = MagicMock(spec=AsyncAPIs)
client.async_.ingestion = MagicMock()
return client
Expand Down
Loading
Loading