|
1 | 1 | import logging |
| 2 | +import re |
2 | 3 | import traceback |
3 | 4 | from collections import defaultdict |
4 | 5 | from concurrent.futures import as_completed |
5 | 6 | from concurrent.futures import ThreadPoolExecutor |
6 | 7 | from typing import Dict |
| 8 | +from typing import Generator |
7 | 9 | from typing import List |
8 | 10 | from typing import Union |
9 | 11 |
|
@@ -386,6 +388,88 @@ def execute(self) -> Response: |
386 | 388 | return self._response |
387 | 389 |
|
388 | 390 |
|
| 391 | +class GenerateItems(BaseReportableUseCase): |
| 392 | + CHUNK_SIZE = 500 |
| 393 | + INVALID_CHARS_PATTERN = re.compile(r"[<>:\"'/\\|?*&$!+]") |
| 394 | + |
| 395 | + def __init__( |
| 396 | + self, |
| 397 | + reporter: Reporter, |
| 398 | + project: ProjectEntity, |
| 399 | + folder: FolderEntity, |
| 400 | + name_prefix: str, |
| 401 | + count: int, |
| 402 | + service_provider: BaseServiceProvider, |
| 403 | + ): |
| 404 | + super().__init__(reporter) |
| 405 | + self._project = project |
| 406 | + self._folder = folder |
| 407 | + self._name_prefix = name_prefix |
| 408 | + self._count = count |
| 409 | + self._service_provider = service_provider |
| 410 | + |
| 411 | + def validate_name(self): |
| 412 | + if ( |
| 413 | + len(self._name_prefix) > 114 |
| 414 | + or self.INVALID_CHARS_PATTERN.search(self._name_prefix) is not None |
| 415 | + ): |
| 416 | + raise AppException("Invalid item name.") |
| 417 | + |
| 418 | + def validate_limitations(self): |
| 419 | + response = self._service_provider.get_limitations( |
| 420 | + project=self._project, folder=self._folder |
| 421 | + ) |
| 422 | + if not response.ok: |
| 423 | + raise AppValidationException(response.error) |
| 424 | + if self._count > response.data.folder_limit.remaining_image_count: |
| 425 | + raise AppValidationException(constants.ATTACH_FOLDER_LIMIT_ERROR_MESSAGE) |
| 426 | + if self._count > response.data.project_limit.remaining_image_count: |
| 427 | + raise AppValidationException(constants.ATTACH_PROJECT_LIMIT_ERROR_MESSAGE) |
| 428 | + if ( |
| 429 | + response.data.user_limit |
| 430 | + and self._count > response.data.user_limit.remaining_image_count |
| 431 | + ): |
| 432 | + raise AppValidationException(constants.ATTACH_USER_LIMIT_ERROR_MESSAGE) |
| 433 | + |
| 434 | + def validate_project_type(self): |
| 435 | + if self._project.type != constants.ProjectType.MULTIMODAL: |
| 436 | + raise AppException( |
| 437 | + "This function is only supported for Multimodal projects." |
| 438 | + ) |
| 439 | + |
| 440 | + @staticmethod |
| 441 | + def generate_attachments( |
| 442 | + name: str, start: int, end: int, chunk_size: int |
| 443 | + ) -> Generator[List[Attachment], None, None]: |
| 444 | + chunk = [] |
| 445 | + for i in range(start, end + 1): |
| 446 | + chunk.append(Attachment(name=f"{name}_{i:05d}", path="custom_llm")) |
| 447 | + if len(chunk) == chunk_size: |
| 448 | + yield chunk |
| 449 | + chunk = [] |
| 450 | + if chunk: |
| 451 | + yield chunk |
| 452 | + |
| 453 | + def execute(self) -> Response: |
| 454 | + if self.is_valid(): |
| 455 | + attached_items_count = 0 |
| 456 | + for chunk in self.generate_attachments( |
| 457 | + self._name_prefix, start=1, end=self._count, chunk_size=self.CHUNK_SIZE |
| 458 | + ): |
| 459 | + backend_response = self._service_provider.items.attach( |
| 460 | + project=self._project, |
| 461 | + folder=self._folder, |
| 462 | + attachments=chunk, |
| 463 | + upload_state_code=3, |
| 464 | + ) |
| 465 | + if not backend_response.ok: |
| 466 | + self._response.errors = AppException(backend_response.error) |
| 467 | + return self._response |
| 468 | + attached_items_count += len(chunk) |
| 469 | + self._response.data = attached_items_count |
| 470 | + return self._response |
| 471 | + |
| 472 | + |
389 | 473 | class CopyItems(BaseReportableUseCase): |
390 | 474 | """ |
391 | 475 | Copy items in bulk between folders in a project. |
|
0 commit comments