digitalkin 0.2.13__py3-none-any.whl → 0.2.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,8 @@
1
1
  """This module contains the abstract base class for filesystem strategies."""
2
2
 
3
3
  from abc import ABC, abstractmethod
4
- from enum import Enum, auto
4
+ from datetime import datetime
5
+ from typing import Any
5
6
 
6
7
  from pydantic import BaseModel, Field
7
8
 
@@ -9,63 +10,197 @@ from digitalkin.services.base_strategy import BaseStrategy
9
10
 
10
11
 
11
12
  class FilesystemServiceError(Exception):
12
- """Base exception for Setup service errors."""
13
+ """Base exception for Filesystem service errors."""
13
14
 
14
15
 
15
- class FileType(Enum):
16
- """Enum defining the types of data that can be stored."""
17
-
18
- DOCUMENT = auto()
19
- IMAGE = auto()
20
- VIDEO = auto()
21
- AUDIO = auto()
22
- ARCHIVE = auto()
23
- OTHER = auto()
24
-
25
-
26
- class FilesystemData(BaseModel):
16
+ class FilesystemRecord(BaseModel):
27
17
  """Data model for filesystem operations."""
28
18
 
29
- kin_context: str = Field(description="The context of the file in the filesystem")
19
+ id: str = Field(description="Unique identifier for the file (UUID)")
20
+ context: str = Field(description="The context of the file in the filesystem")
30
21
  name: str = Field(description="The name of the file")
31
- file_type: FileType = Field(default=FileType.DOCUMENT, description="The type of data stored")
32
- url: str = Field(description="The URL of the file in the filesystem")
22
+ file_type: str = Field(default="UNSPECIFIED", description="The type of data stored")
23
+ content_type: str = Field(default="application/octet-stream", description="The MIME type of the file")
24
+ size_bytes: int = Field(default=0, description="Size of the file in bytes")
25
+ checksum: str = Field(default="", description="SHA-256 checksum of the file content")
26
+ metadata: dict[str, Any] | None = Field(default=None, description="Additional metadata for the file")
27
+ storage_url: str = Field(description="Internal URL for accessing the file content")
28
+ status: str = Field(default="UNSPECIFIED", description="Current status of the file")
29
+ content: bytes | None = Field(default=None, description="The content of the file")
30
+
31
+
32
+ class FileFilter(BaseModel):
33
+ """Filter criteria for querying files."""
34
+
35
+ names: list[str] | None = Field(default=None, description="Filter by file names (exact matches)")
36
+ file_ids: list[str] | None = Field(default=None, description="Filter by file IDs")
37
+ file_types: list[str] | None = Field(default=None, description="Filter by file types")
38
+ created_after: datetime | None = Field(default=None, description="Filter files created after this timestamp")
39
+ created_before: datetime | None = Field(default=None, description="Filter files created before this timestamp")
40
+ updated_after: datetime | None = Field(default=None, description="Filter files updated after this timestamp")
41
+ updated_before: datetime | None = Field(default=None, description="Filter files updated before this timestamp")
42
+ status: str | None = Field(default=None, description="Filter by file status")
43
+ content_type_prefix: str | None = Field(default=None, description="Filter by content type prefix (e.g., 'image/')")
44
+ min_size_bytes: int | None = Field(default=None, description="Filter files with minimum size")
45
+ max_size_bytes: int | None = Field(default=None, description="Filter files with maximum size")
46
+ prefix: str | None = Field(default=None, description="Filter by path prefix (e.g., 'folder1/')")
47
+ content_type: str | None = Field(default=None, description="Filter by content type")
48
+
49
+
50
+ class UploadFileData(BaseModel):
51
+ """Data model for uploading a file."""
52
+
53
+ content: bytes = Field(description="The content of the file")
54
+ name: str = Field(description="The name of the file")
55
+ file_type: str = Field(description="The type of the file")
56
+ content_type: str | None = Field(default=None, description="The content type of the file")
57
+ metadata: dict[str, Any] | None = Field(default=None, description="The metadata of the file")
58
+ replace_if_exists: bool = Field(default=False, description="Whether to replace the file if it already exists")
33
59
 
34
60
 
35
61
  class FilesystemStrategy(BaseStrategy, ABC):
36
- """Abstract base class for filesystem strategies."""
62
+ """Abstract base class for filesystem strategies.
63
+
64
+ This strategy provides comprehensive file management capabilities including
65
+ upload, retrieval, update, and deletion operations with rich metadata support,
66
+ filtering, and pagination.
67
+ """
37
68
 
38
- def __init__(self, mission_id: str, setup_version_id: str, config: dict[str, str]) -> None:
69
+ def __init__(self, mission_id: str, setup_version_id: str, config: dict[str, Any] | None = None) -> None:
39
70
  """Initialize the strategy.
40
71
 
41
72
  Args:
42
73
  mission_id: The ID of the mission this strategy is associated with
43
74
  setup_version_id: The ID of the setup version this strategy is associated with
44
- config: configuration dictionary for the filesystem strategy
75
+ config: Configuration for the filesystem strategy
45
76
  """
46
77
  super().__init__(mission_id, setup_version_id)
47
- self.config: dict[str, str] = config
78
+ self.config = config
48
79
 
49
80
  @abstractmethod
50
- def upload(self, content: bytes, name: str, file_type: FileType) -> FilesystemData:
51
- """Create a new file in the filesystem."""
81
+ def upload_files(
82
+ self,
83
+ files: list[UploadFileData],
84
+ ) -> tuple[list[FilesystemRecord], int, int]:
85
+ """Upload multiple files to the system.
52
86
 
53
- @abstractmethod
54
- def get(self, name: str) -> FilesystemData:
55
- """Get file from the filesystem."""
87
+ This method allows batch uploading of files with validation and
88
+ error handling for each individual file. Files are processed
89
+ atomically - if one fails, others may still succeed.
90
+
91
+ Args:
92
+ files: List of tuples containing (content, name, file_type, content_type, metadata, replace_if_exists)
93
+
94
+ Returns:
95
+ tuple[list[FilesystemRecord], int, int]: List of uploaded files, total uploaded count, total failed count
96
+ """
56
97
 
57
98
  @abstractmethod
58
- def get_batch(self, names: list[str]) -> dict[str, FilesystemData | None]:
59
- """Get files from the filesystem."""
99
+ def get_file(
100
+ self,
101
+ file_id: str,
102
+ *,
103
+ include_content: bool = False,
104
+ ) -> tuple[FilesystemRecord, bytes | None]:
105
+ """Get a specific file by ID or name.
106
+
107
+ This method fetches detailed information about a single file,
108
+ with optional content inclusion. Supports lookup by either
109
+ unique ID or name within a context.
110
+
111
+ Args:
112
+ file_id: The ID of the file to be retrieved
113
+ include_content: Whether to include file content in response
114
+
115
+ Returns:
116
+ tuple[FilesystemRecord, bytes | None]: Metadata about the retrieved file and optional content
117
+ """
60
118
 
61
119
  @abstractmethod
62
- def get_all(self) -> list[FilesystemData]:
63
- """Get all files from the filesystem."""
120
+ def get_files(
121
+ self,
122
+ filters: FileFilter,
123
+ *,
124
+ list_size: int = 100,
125
+ offset: int = 0,
126
+ order: str | None = None,
127
+ include_content: bool = False,
128
+ ) -> tuple[list[FilesystemRecord], int]:
129
+ """Get multiple files by various criteria.
130
+
131
+ This method provides efficient retrieval of multiple files using:
132
+ - File IDs
133
+ - File names
134
+ - Path prefix
135
+ With support for:
136
+ - Pagination for large result sets
137
+ - Optional content inclusion
138
+ - Total count of matching files
139
+
140
+ Args:
141
+ filters: Filter criteria for the files
142
+ list_size: Number of files to return per page
143
+ offset: Offset to start listing files from
144
+ order: Field to order results by
145
+ include_content: Whether to include file content in response
146
+
147
+ Returns:
148
+ tuple[list[FilesystemRecord], int]: List of files and total count
149
+ """
64
150
 
65
151
  @abstractmethod
66
- def update(self, name: str, content: bytes, file_type: FileType) -> FilesystemData:
67
- """Update files in the filesystem."""
152
+ def update_file(
153
+ self,
154
+ file_id: str,
155
+ content: bytes | None = None,
156
+ file_type: str | None = None,
157
+ content_type: str | None = None,
158
+ metadata: dict[str, Any] | None = None,
159
+ new_name: str | None = None,
160
+ status: str | None = None,
161
+ ) -> FilesystemRecord:
162
+ """Update file metadata, content, or both.
163
+
164
+ This method allows updating various aspects of a file:
165
+ - Rename files
166
+ - Update content and content type
167
+ - Modify metadata
168
+ - Create new versions
169
+
170
+ Args:
171
+ file_id: The ID of the file to be updated
172
+ content: Optional new content of the file
173
+ file_type: Optional new type of data
174
+ content_type: Optional new MIME type
175
+ metadata: Optional new metadata (will merge with existing)
176
+ new_name: Optional new name for the file
177
+ status: Optional new status for the file
178
+
179
+ Returns:
180
+ FilesystemRecord: Metadata about the updated file
181
+ """
68
182
 
69
183
  @abstractmethod
70
- def delete(self, name: str) -> bool:
71
- """Delete file from the filesystem."""
184
+ def delete_files(
185
+ self,
186
+ filters: FileFilter,
187
+ *,
188
+ permanent: bool = False,
189
+ force: bool = False,
190
+ ) -> tuple[dict[str, bool], int, int]:
191
+ """Delete multiple files.
192
+
193
+ This method supports batch deletion of files with options for:
194
+ - Soft deletion (marking as deleted)
195
+ - Permanent deletion
196
+ - Force deletion of files in use
197
+ - Individual error reporting per file
198
+
199
+ Args:
200
+ filters: Filter criteria for the files
201
+ permanent: Whether to permanently delete the files
202
+ force: Whether to force delete even if files are in use
203
+
204
+ Returns:
205
+ tuple[dict[str, bool], int, int]: Results per file, total deleted count, total failed count
206
+ """
@@ -1,26 +1,23 @@
1
- """Grpc filesystem."""
1
+ """gRPC filesystem implementation."""
2
2
 
3
3
  from collections.abc import Generator
4
4
  from contextlib import contextmanager
5
5
  from typing import Any
6
6
 
7
- from digitalkin_proto.digitalkin.filesystem.v2 import (
8
- filesystem_pb2,
9
- filesystem_service_pb2_grpc,
10
- )
11
- from digitalkin_proto.digitalkin.filesystem.v2.filesystem_pb2 import (
12
- FileType as FileTypeProto,
13
- )
7
+ from digitalkin_proto.digitalkin.filesystem.v1 import filesystem_pb2, filesystem_service_pb2_grpc
8
+ from google.protobuf import struct_pb2
9
+ from google.protobuf.json_format import MessageToDict
14
10
 
15
11
  from digitalkin.grpc_servers.utils.exceptions import ServerError
16
12
  from digitalkin.grpc_servers.utils.grpc_client_wrapper import GrpcClientWrapper
17
13
  from digitalkin.grpc_servers.utils.models import ClientConfig
18
14
  from digitalkin.logger import logger
19
15
  from digitalkin.services.filesystem.filesystem_strategy import (
20
- FilesystemData,
16
+ FileFilter,
17
+ FilesystemRecord,
21
18
  FilesystemServiceError,
22
19
  FilesystemStrategy,
23
- FileType,
20
+ UploadFileData,
24
21
  )
25
22
 
26
23
 
@@ -54,161 +51,272 @@ class GrpcFilesystem(FilesystemStrategy, GrpcClientWrapper):
54
51
  logger.exception(msg)
55
52
  raise FilesystemServiceError(msg) from e
56
53
 
54
+ @staticmethod
55
+ def _file_type_to_enum(file_type: str) -> filesystem_pb2.FileType:
56
+ """Convert a file type string to a FileType enum.
57
+
58
+ Args:
59
+ file_type: The file type string to convert
60
+
61
+ Returns:
62
+ filesystem_pb2.FileType: The converted file type enum
63
+ """
64
+ if not file_type.upper().startswith("FILE_TYPE_"):
65
+ file_type = f"FILE_TYPE_{file_type.upper()}"
66
+ try:
67
+ return getattr(filesystem_pb2.FileType, file_type.upper())
68
+ except AttributeError:
69
+ return filesystem_pb2.FileType.FILE_TYPE_UNSPECIFIED
70
+
71
+ @staticmethod
72
+ def _file_status_to_enum(file_status: str) -> filesystem_pb2.FileStatus:
73
+ """Convert a file status string to a FileStatus enum.
74
+
75
+ Args:
76
+ file_status: The file status string to convert
77
+
78
+ Returns:
79
+ filesystem_pb2.FileStatus: The converted file status enum
80
+ """
81
+ if not file_status.upper().startswith("FILE_STATUS_"):
82
+ file_status = f"FILE_STATUS_{file_status.upper()}"
83
+ try:
84
+ return getattr(filesystem_pb2.FileStatus, file_status.upper())
85
+ except AttributeError:
86
+ return filesystem_pb2.FileStatus.FILE_STATUS_UNSPECIFIED
87
+
88
+ def _filter_to_proto(self, filters: FileFilter) -> filesystem_pb2.FileFilter:
89
+ """Convert a FileFilter to a FileFilter proto message.
90
+
91
+ Args:
92
+ filters: The FileFilter to convert
93
+
94
+ Returns:
95
+ filesystem_pb2.FileFilter: The converted FileFilter proto message
96
+ """
97
+ return filesystem_pb2.FileFilter(
98
+ context=self.mission_id,
99
+ **filters.model_dump(exclude={"file_types", "status"}),
100
+ file_types=[self._file_type_to_enum(file_type) for file_type in filters.file_types]
101
+ if filters.file_types
102
+ else None,
103
+ status=self._file_status_to_enum(filters.status) if filters.status else None,
104
+ )
105
+
106
+ def _file_proto_to_data(self, file: filesystem_pb2.File, content: bytes | None = None) -> FilesystemRecord:
107
+ """Convert a File proto message to FilesystemRecord.
108
+
109
+ Args:
110
+ file: The File proto message to convert
111
+ content: The content of the file
112
+
113
+ Returns:
114
+ FilesystemRecord: The converted data
115
+ """
116
+ return FilesystemRecord(
117
+ id=file.file_id,
118
+ context=self.mission_id,
119
+ name=file.name,
120
+ file_type=filesystem_pb2.FileType.Name(file.file_type),
121
+ content_type=file.content_type,
122
+ size_bytes=file.size_bytes,
123
+ checksum=file.checksum,
124
+ metadata=MessageToDict(file.metadata),
125
+ storage_url=file.storage_url,
126
+ status=filesystem_pb2.FileStatus.Name(file.status),
127
+ content=content,
128
+ )
129
+
57
130
  def __init__(
58
131
  self,
59
132
  mission_id: str,
60
133
  setup_version_id: str,
61
- config: dict[str, str],
62
134
  client_config: ClientConfig,
63
- **kwargs, # noqa: ANN003, ARG002
135
+ config: dict[str, Any] | None = None,
64
136
  ) -> None:
65
- """Initialize the default filesystem strategy.
137
+ """Initialize the gRPC filesystem strategy.
66
138
 
67
139
  Args:
68
140
  mission_id: The ID of the mission this strategy is associated with
69
141
  setup_version_id: The ID of the setup version this strategy is associated with
70
- config: A dictionary mapping names to Pydantic model classes
71
- client_config: The server configuration object
72
- kwargs: other optional arguments to pass to the parent class constructor
142
+ client_config: Configuration for the gRPC client connection
143
+ config: Configuration for the filesystem strategy
73
144
  """
74
145
  super().__init__(mission_id, setup_version_id, config)
146
+ self.service_name = "FilesystemService"
75
147
  channel = self._init_channel(client_config)
76
148
  self.stub = filesystem_service_pb2_grpc.FilesystemServiceStub(channel)
77
- logger.info("Channel client 'Filesystem' initialized succesfully")
149
+ logger.debug("Channel client 'Filesystem' initialized succesfully")
78
150
 
79
- def upload(self, content: bytes, name: str, file_type: FileType) -> FilesystemData:
80
- """Create a new file in the file system.
151
+ def upload_files(
152
+ self,
153
+ files: list[UploadFileData],
154
+ ) -> tuple[list[FilesystemRecord], int, int]:
155
+ """Upload multiple files to the filesystem.
81
156
 
82
157
  Args:
83
- content: The content of the file to be uploaded
84
- name: The name of the file to be created
85
- file_type: The type of data being uploaded
158
+ files: List of tuples containing (content, name, file_type, content_type, metadata, replace_if_exists)
86
159
 
87
160
  Returns:
88
- FilesystemData: Metadata about the uploaded file
89
-
90
- Raises:
91
- ValueError: If the file already exists
161
+ tuple[list[FilesystemRecord], int, int]: List of uploaded files, total uploaded count, total failed count
92
162
  """
93
- with GrpcFilesystem._handle_grpc_errors("UploadFile"):
94
- request = filesystem_pb2.UploadFileRequest(
95
- kin_context=self.mission_id,
96
- name=name,
97
- file_type=file_type.name,
98
- content=content,
99
- )
100
- response: filesystem_pb2.UploadFileResponse = self.exec_grpc_query("UploadFile", request)
101
- return FilesystemData(
102
- kin_context=response.file.kin_context,
103
- name=response.file.name,
104
- file_type=FileType[FileTypeProto.Name(response.file.file_type)],
105
- url=response.file.url,
106
- )
163
+ logger.debug("Uploading %d files", len(files))
164
+ with GrpcFilesystem._handle_grpc_errors("UploadFiles"):
165
+ upload_files: list[filesystem_pb2.UploadFileData] = []
166
+ for file in files:
167
+ metadata_struct: struct_pb2.Struct | None = None
168
+ if file.metadata:
169
+ metadata_struct = struct_pb2.Struct()
170
+ metadata_struct.update(file.metadata)
171
+ upload_files.append(
172
+ filesystem_pb2.UploadFileData(
173
+ context=self.mission_id,
174
+ name=file.name,
175
+ file_type=self._file_type_to_enum(file.file_type),
176
+ content_type=file.content_type or "application/octet-stream",
177
+ content=file.content,
178
+ metadata=metadata_struct,
179
+ status=filesystem_pb2.FileStatus.FILE_STATUS_UPLOADING,
180
+ replace_if_exists=file.replace_if_exists,
181
+ )
182
+ )
183
+ request = filesystem_pb2.UploadFilesRequest(files=upload_files)
184
+ response: filesystem_pb2.UploadFilesResponse = self.exec_grpc_query("UploadFiles", request)
185
+ results = [self._file_proto_to_data(result.file) for result in response.results if result.HasField("file")]
186
+ logger.debug("Uploaded files: %s", results)
187
+ return results, response.total_uploaded, response.total_failed
107
188
 
108
- def get(self, name: str) -> FilesystemData:
109
- """Get file from the filesystem.
189
+ def get_file(
190
+ self,
191
+ file_id: str,
192
+ *,
193
+ include_content: bool = False,
194
+ ) -> FilesystemRecord:
195
+ """Get a file from the filesystem.
110
196
 
111
197
  Args:
112
- name: The name of the file to be retrieved
198
+ file_id: The ID of the file to be retrieved
199
+ include_content: Whether to include file content in response
113
200
 
114
201
  Returns:
115
- FilesystemData: Metadata about the retrieved file
202
+ FilesystemRecord: Metadata about the retrieved file
203
+
204
+ Raises:
205
+ FilesystemServiceError: If there is an error retrieving the file
116
206
  """
117
- with GrpcFilesystem._handle_grpc_errors("GetFileByName"):
118
- request = filesystem_pb2.GetFileByNameRequest(name=name)
119
- response: filesystem_pb2.GetFileByNameResponse = self.exec_grpc_query("GetFileByName", request)
120
- return FilesystemData(
121
- kin_context=response.file.kin_context,
122
- name=response.file.name,
123
- file_type=FileType[FileTypeProto.Name(response.file.file_type)],
124
- url=response.file.url,
207
+ with GrpcFilesystem._handle_grpc_errors("GetFile"):
208
+ request = filesystem_pb2.GetFileRequest(
209
+ context=self.mission_id,
210
+ file_id=file_id,
211
+ include_content=include_content,
125
212
  )
126
213
 
127
- def update(self, name: str, content: bytes, file_type: FileType) -> FilesystemData:
128
- """Update files in the filesystem.
214
+ response: filesystem_pb2.GetFileResponse = self.exec_grpc_query("GetFile", request)
215
+
216
+ return self._file_proto_to_data(response.file, response.content)
217
+
218
+ def update_file(
219
+ self,
220
+ file_id: str,
221
+ content: bytes | None = None,
222
+ file_type: str | None = None,
223
+ content_type: str | None = None,
224
+ metadata: dict[str, Any] | None = None,
225
+ new_name: str | None = None,
226
+ status: str | None = None,
227
+ ) -> FilesystemRecord:
228
+ """Update a file in the filesystem.
129
229
 
130
230
  Args:
131
- name: The name of the file to be updated
132
- content: The new content of the file
133
- file_type: The type of data being uploaded
231
+ file_id: The id of the file to be updated
232
+ content: Optional new content of the file
233
+ file_type: Optional new type of data
234
+ content_type: Optional new MIME type
235
+ metadata: Optional new metadata (will merge with existing)
236
+ new_name: Optional new name for the file
237
+ status: Optional new status for the file
134
238
 
135
239
  Returns:
136
- FilesystemData: Metadata about the updated file
240
+ FilesystemRecord: Metadata about the updated file
241
+
242
+ Raises:
243
+ FilesystemServiceError: If there is an error during update
137
244
  """
138
245
  with GrpcFilesystem._handle_grpc_errors("UpdateFile"):
139
246
  request = filesystem_pb2.UpdateFileRequest(
140
- kin_context=self.mission_id,
141
- name=name,
142
- file_type=file_type.name,
247
+ context=self.mission_id,
248
+ file_id=file_id,
143
249
  content=content,
144
- )
145
- response: filesystem_pb2.UpdateFileResponse = self.exec_grpc_query("UpdateFile", request)
146
- return FilesystemData(
147
- kin_context=response.file.kin_context,
148
- name=response.file.name,
149
- file_type=FileType[FileTypeProto.Name(response.file.file_type)],
150
- url=response.file.url,
250
+ file_type=self._file_type_to_enum(file_type) if file_type else None,
251
+ content_type=content_type,
252
+ new_name=new_name,
253
+ status=self._file_status_to_enum(status) if status else None,
151
254
  )
152
255
 
153
- def delete(self, name: str) -> bool:
154
- """Delete files from the filesystem.
256
+ if metadata:
257
+ request.metadata.update(metadata)
155
258
 
156
- Args:
157
- name: The name of the file to be deleted
259
+ response: filesystem_pb2.UpdateFileResponse = self.exec_grpc_query("UpdateFile", request)
260
+ return self._file_proto_to_data(response.result.file)
158
261
 
159
- Returns:
160
- int: 1 if the file was deleted successfully, 0 if it didn't exist, None on error
161
- """
162
- with GrpcFilesystem._handle_grpc_errors("DeleteFile"):
163
- request = filesystem_pb2.DeleteFileRequest(name=name)
164
- _: filesystem_pb2.DeleteFileResponse = self.exec_grpc_query("DeleteFile", request)
165
- return True
262
+ def delete_files(
263
+ self,
264
+ filters: FileFilter,
265
+ *,
266
+ permanent: bool = False,
267
+ force: bool = False,
268
+ ) -> tuple[dict[str, bool], int, int]:
269
+ """Delete multiple files from the filesystem.
166
270
 
167
- def get_all(self) -> list[FilesystemData]:
168
- """Get all files from the filesystem.
271
+ Args:
272
+ filters: Filter criteria for the files
273
+ permanent: Whether to permanently delete the files
274
+ force: Whether to force delete even if files are in use
169
275
 
170
276
  Returns:
171
- list[FilesystemData]: A list of all files in the filesystem
277
+ tuple[dict[str, bool], int, int]: Results per file, total deleted count, total failed count
172
278
  """
173
- with GrpcFilesystem._handle_grpc_errors("GetFilesByKinContext"):
174
- request = filesystem_pb2.GetFilesByKinContextRequest(kin_context=self.mission_id)
175
- response: filesystem_pb2.GetFilesByKinContextResponse = self.exec_grpc_query(
176
- "GetFilesByKinContext", request
279
+ with GrpcFilesystem._handle_grpc_errors("DeleteFiles"):
280
+ request = filesystem_pb2.DeleteFilesRequest(
281
+ context=self.mission_id,
282
+ filters=self._filter_to_proto(filters),
283
+ permanent=permanent,
284
+ force=force,
177
285
  )
178
- return [
179
- FilesystemData(
180
- kin_context=file.kin_context,
181
- name=file.name,
182
- file_type=FileType[FileTypeProto.Name(file.file_type)],
183
- url=file.url,
184
- )
185
- for file in response.files
186
- ]
187
286
 
188
- def get_batch(self, names: list[str]) -> dict[str, FilesystemData | None]:
189
- """Get files from the filesystem.
287
+ response: filesystem_pb2.DeleteFilesResponse = self.exec_grpc_query("DeleteFiles", request)
288
+ return dict(response.results), response.total_deleted, response.total_failed
289
+
290
+ def get_files(
291
+ self,
292
+ filters: FileFilter,
293
+ *,
294
+ list_size: int = 100,
295
+ offset: int = 0,
296
+ order: str | None = None,
297
+ include_content: bool = False,
298
+ ) -> tuple[list[FilesystemRecord], int]:
299
+ """Get multiple files from the filesystem.
190
300
 
191
301
  Args:
192
- names: The names of the files to be retrieved
302
+ filters: Filter criteria for the files
303
+ list_size: Number of files to return per page
304
+ offset: Offset to start from
305
+ order: Field to order results by
306
+ include_content: Whether to include file content in response
193
307
 
194
308
  Returns:
195
- list[FilesystemData]: A list of metadata about the retrieved files
309
+ tuple[list[FilesystemRecord], int]: List of files and total count
196
310
  """
197
- with GrpcFilesystem._handle_grpc_errors("GetFilesByNames"):
198
- request = filesystem_pb2.GetFilesByNamesRequest(names=names)
199
- response: filesystem_pb2.GetFilesByNamesResponse = self.exec_grpc_query("GetFilesByNames", request)
200
- result: dict[str, FilesystemData | None] = {}
201
- for name, file_result in response.files.items():
202
- which_field = file_result.WhichOneof("result")
203
- if which_field == "file":
204
- result[name] = FilesystemData(
205
- kin_context=file_result.file.kin_context,
206
- name=file_result.file.name,
207
- file_type=FileType[FileTypeProto.Name(file_result.file.file_type)],
208
- url=file_result.file.url,
209
- )
210
- elif which_field == "error":
211
- # Handle error case
212
- result[name] = None
213
- logger.warning("Error retrieving file '%s': %s", name, file_result.error)
214
- return result
311
+ with GrpcFilesystem._handle_grpc_errors("GetFiles"):
312
+ request = filesystem_pb2.GetFilesRequest(
313
+ context=self.mission_id,
314
+ filters=self._filter_to_proto(filters),
315
+ include_content=include_content,
316
+ list_size=list_size,
317
+ offset=offset,
318
+ order=order,
319
+ )
320
+ response: filesystem_pb2.GetFilesResponse = self.exec_grpc_query("GetFiles", request)
321
+
322
+ return [self._file_proto_to_data(file) for file in response.files], response.total_count
@@ -139,6 +139,7 @@ class GrpcSetup(SetupStrategy, GrpcClientWrapper):
139
139
  current_setup_version = setup_pb2.SetupVersion(**valid_data.current_setup_version.model_dump())
140
140
 
141
141
  request = setup_pb2.UpdateSetupRequest(
142
+ setup_id=valid_data.id,
142
143
  name=valid_data.name,
143
144
  owner_id=valid_data.owner_id or "",
144
145
  current_setup_version=current_setup_version,