|
21 | 21 | from citrine._session import Session |
22 | 22 | from citrine._utils.functions import rewrite_s3_links_locally |
23 | 23 | from citrine._utils.functions import write_file_locally, format_escaped_url |
24 | | -from citrine.exceptions import NotFound |
| 24 | + |
25 | 25 | from citrine.jobs.job import JobSubmissionResponse, _poll_for_job_completion |
26 | 26 | from citrine.resources.response import Response |
27 | 27 | from gemd.entity.bounds.base_bounds import BaseBounds |
28 | 28 | from gemd.entity.file_link import FileLink as GEMDFileLink |
| 29 | +from gemd.enumeration.base_enumeration import BaseEnumeration |
29 | 30 |
|
30 | 31 | logger = getLogger(__name__) |
31 | 32 |
|
32 | 33 |
|
| 34 | +class SearchFileFilterTypeEnum(BaseEnumeration): |
| 35 | + """ |
| 36 | + The type of the filter used to search for files. |
| 37 | +
|
| 38 | + * SEARCH_BY_NAME: |
| 39 | + Search a file by name in a specific dataset, |
| 40 | + returns by default the last version or a specific one |
| 41 | + * SEARCH_BY_VERSION_ID: |
| 42 | + Search by a specific file version id |
| 43 | + * SEARCH_BY_DATASET_FILE_ID: |
| 44 | + Search either the last version or a specific version number for a specific dataset file id |
| 45 | +
|
| 46 | + """ |
| 47 | + |
| 48 | + NAME_SEARCH = "search_by_name" |
| 49 | + VERSION_ID_SEARCH = "search_by_version_id" |
| 50 | + DATASET_FILE_ID_SEARCH = "search_by_dataset_file_id" |
| 51 | + |
| 52 | + |
33 | 53 | class _Uploader: |
34 | 54 | """Holds the many parameters that are generated and used during file upload.""" |
35 | 55 |
|
@@ -199,7 +219,7 @@ def _get_path_from_file_link(self, file_link: FileLink, |
199 | 219 | # Use this sessions project/dataset credentials and the URL's file / version |
200 | 220 | file_id, version_id = self._get_ids_from_url(file_link.url) |
201 | 221 | if file_id is None: |
202 | | - raise ValueError(f"FileLink did not contain a Citrine platform file URL.") |
| 222 | + raise ValueError("FileLink did not contain a Citrine platform file URL.") |
203 | 223 | return self._get_path(uid=file_id, version=version_id, action=action) |
204 | 224 |
|
205 | 225 | def build(self, data: dict) -> FileLink: |
@@ -263,9 +283,8 @@ def _as_dict_from_resource(self, file: dict): |
263 | 283 | # FIXME While the 'id' field is supposed to be the file ID, it contains the version |
264 | 284 | # for some reason. Needs to be fixed on back end. PLA-9482 |
265 | 285 | filename = file['filename'] |
266 | | - # file_id = file['id'] |
267 | | - # version_id = file['version'] |
268 | | - file_id, version_id = self._get_ids_from_url(file['versioned_url']) |
| 286 | + file_id = file['id'] |
| 287 | + version_id = file['version'] |
269 | 288 |
|
270 | 289 | file_dict = { |
271 | 290 | 'url': self._get_path(uid=file_id, version=version_id), |
@@ -303,59 +322,40 @@ def get(self, |
303 | 322 | raise TypeError(f"Version can only be resolved from str, int or UUID." |
304 | 323 | f"Instead got {type(uid)} {uid}.") |
305 | 324 |
|
306 | | - try: # Check if the uid string is actually a UUID |
307 | | - if isinstance(uid, str): |
| 325 | + if isinstance(uid, str): |
| 326 | + try: # Check if the uid string is actually a UUID |
308 | 327 | uid = UUID(uid) |
309 | | - except ValueError: |
310 | | - pass |
311 | | - |
312 | | - try: # Check if the version string is actually a UUID |
313 | | - if isinstance(version, str): |
314 | | - version = UUID(version) |
315 | | - except ValueError: |
316 | | - pass |
317 | | - |
318 | | - try: # Check if the version string is actually an int / version number |
319 | | - if isinstance(version, str): |
320 | | - version = int(version) |
321 | | - except ValueError: |
322 | | - pass |
| 328 | + except ValueError: |
| 329 | + pass |
323 | 330 |
|
324 | 331 | if isinstance(version, str): |
325 | | - raise ValueError( |
326 | | - f"Version {version} could not be converted to either an int or a UUID" |
327 | | - ) |
| 332 | + try: # Check if the version string is actually a UUID |
| 333 | + version = UUID(version) |
| 334 | + except ValueError: |
| 335 | + try: # Check if the version string is actually an int / version number |
| 336 | + version = int(version) |
| 337 | + except ValueError: |
| 338 | + raise ValueError( |
| 339 | + f"Version {version} could not be converted to either an int or a UUID" |
| 340 | + ) |
328 | 341 |
|
329 | 342 | if isinstance(uid, str): |
330 | | - # Assume it's the filename on platform; resolve to UUID |
331 | | - match = next((f for f in self.list() if uid == f.filename), None) |
332 | | - if match is None: |
333 | | - raise NotFound(f"Found no file named {uid}") |
334 | | - match_file, match_version = self._get_ids_from_url(match.url) |
335 | | - if version is None or version == match_version: |
336 | | - # Done; the list endpoint always returns the most recent version |
337 | | - return match |
338 | | - |
339 | | - # Stash the now-resolved UUID to match with a version |
340 | | - uid = match_file |
341 | | - |
342 | | - if isinstance(version, UUID): |
343 | | - # Can only return 0 or 1 result |
344 | | - path = self._get_path(uid=uid, version=version) |
345 | | - data = self.session.get_resource(path, version=self._api_version) |
346 | | - return self.build(self._as_dict_from_resource(data)) |
347 | | - |
348 | | - # version is an int / version number |
349 | | - path = self._get_path(uid=uid) |
350 | | - data = self.session.get_resource(path, version=self._api_version)['files'] |
351 | | - if version is None: |
352 | | - recent = max(data, key=lambda x: x['version_number']) |
353 | | - return self.build(self._as_dict_from_resource(recent)) |
354 | | - for result in data: |
355 | | - if result['version_number'] == version: |
356 | | - return self.build(self._as_dict_from_resource(result)) |
357 | | - |
358 | | - raise NotFound(f"Found file, but no version {version}") |
| 343 | + # Assume it's the filename on platform; |
| 344 | + if version is None or isinstance(version, int): |
| 345 | + file = self._search_by_file_version_id(file_version_id=version) |
| 346 | + else: # We did our type checks earlier; version is an int or None |
| 347 | + file = self._search_by_file_name(dset_id=self.dataset_id, |
| 348 | + file_name=uid, |
| 349 | + file_version_number=version) |
| 350 | + else: # We did our type checks earlier; uid is a UUID |
| 351 | + if isinstance(version, UUID): |
| 352 | + file = self._search_by_file_version_id(file_version_id=version) |
| 353 | + else: # We did our type checks earlier; version is an int or None |
| 354 | + file = self._search_by_dataset_file_id(dataset_file_id=uid, |
| 355 | + dset_id=self.dataset_id, |
| 356 | + file_version_number=version) |
| 357 | + |
| 358 | + return file |
359 | 359 |
|
360 | 360 | def upload(self, *, file_path: Union[str, Path], dest_name: str = None) -> FileLink: |
361 | 361 | """ |
@@ -447,6 +447,119 @@ def _make_upload_request(self, file_path: Path, dest_name: str): |
447 | 447 | "{}".format(upload_request)) |
448 | 448 | return uploader |
449 | 449 |
|
| 450 | + def _search_by_file_name(self, |
| 451 | + file_name: str, |
| 452 | + dset_id: UUID, |
| 453 | + file_version_number: Optional[int] = None |
| 454 | + ) -> Optional[FileLink]: |
| 455 | + """ |
| 456 | + Make a request to the backend to search a file by name. |
| 457 | +
|
| 458 | + Note that you can specify a version number, in case you don't, it will |
| 459 | + return the last version by default. |
| 460 | +
|
| 461 | + Parameters |
| 462 | + ---------- |
| 463 | + file_name: str |
| 464 | + The name of the file. |
| 465 | + dset_id: UUID |
| 466 | + UUID that represents a dataset. |
| 467 | + file_version_number: Optional[int] |
| 468 | + As optional, you can send a specific version number. |
| 469 | +
|
| 470 | + Returns |
| 471 | + ------- |
| 472 | + FileLink |
| 473 | + All the data needed for a file. |
| 474 | +
|
| 475 | + """ |
| 476 | + path = self._get_path() + "/search" |
| 477 | + |
| 478 | + search_json = { |
| 479 | + 'fileSearchFilter': |
| 480 | + { |
| 481 | + 'type': SearchFileFilterTypeEnum.NAME_SEARCH.value, |
| 482 | + 'datasetId': str(dset_id), |
| 483 | + 'fileName': file_name, |
| 484 | + 'fileVersionNumber': file_version_number |
| 485 | + } |
| 486 | + } |
| 487 | + |
| 488 | + data = self.session.post_resource(path=path, json=search_json) |
| 489 | + |
| 490 | + return self.build(self._as_dict_from_resource(data['files'][0])) |
| 491 | + |
| 492 | + def _search_by_file_version_id(self, |
| 493 | + file_version_id: UUID |
| 494 | + ) -> Optional[FileLink]: |
| 495 | + """ |
| 496 | + Make a request to the backend to search a file by file version id. |
| 497 | +
|
| 498 | + Parameters |
| 499 | + ---------- |
| 500 | + file_version_id: UUID |
| 501 | + UUID that represents a file version id. |
| 502 | +
|
| 503 | + Returns |
| 504 | + ------- |
| 505 | + FileLink |
| 506 | + All the data needed for a file. |
| 507 | +
|
| 508 | + """ |
| 509 | + path = self._get_path() + "/search" |
| 510 | + |
| 511 | + search_json = { |
| 512 | + 'fileSearchFilter': { |
| 513 | + 'type': SearchFileFilterTypeEnum.VERSION_ID_SEARCH.value, |
| 514 | + 'fileVersionUuid': str(file_version_id) |
| 515 | + } |
| 516 | + } |
| 517 | + |
| 518 | + data = self.session.post_resource(path=path, json=search_json) |
| 519 | + |
| 520 | + return self.build(self._as_dict_from_resource(data['files'][0])) |
| 521 | + |
| 522 | + def _search_by_dataset_file_id(self, |
| 523 | + dataset_file_id: UUID, |
| 524 | + dset_id: UUID, |
| 525 | + file_version_number: Optional[int] = None |
| 526 | + ) -> Optional[FileLink]: |
| 527 | + """ |
| 528 | + Make a request to the backend to search a file by dataset file id. |
| 529 | +
|
| 530 | + Note that you can specify a version number, in case you don't, it will |
| 531 | + return the last version by default. |
| 532 | +
|
| 533 | + Parameters |
| 534 | + ---------- |
| 535 | + dataset_file_id: UUID |
| 536 | + UUID that represents a dataset file id. |
| 537 | + dset_id: UUID |
| 538 | + UUID that represents a dataset. |
| 539 | + file_version_number: Optional[int] |
| 540 | + As optional, you can send a specific version number |
| 541 | +
|
| 542 | + Returns |
| 543 | + ------- |
| 544 | + FileLink |
| 545 | + All the data needed for a file. |
| 546 | +
|
| 547 | + """ |
| 548 | + path = self._get_path() + "/search" |
| 549 | + |
| 550 | + search_json = { |
| 551 | + 'fileSearchFilter': { |
| 552 | + 'type': SearchFileFilterTypeEnum.DATASET_FILE_ID_SEARCH.value, |
| 553 | + 'datasetId': str(dset_id), |
| 554 | + 'datasetFileId': str(dataset_file_id), |
| 555 | + 'fileVersionNumber': file_version_number |
| 556 | + } |
| 557 | + } |
| 558 | + |
| 559 | + data = self.session.post_resource(path=path, json=search_json) |
| 560 | + |
| 561 | + return self.build(self._as_dict_from_resource(data['files'][0])) |
| 562 | + |
450 | 563 | @staticmethod |
451 | 564 | def _mime_type(file_path: Path): |
452 | 565 | # This string coercion is for supporting pathlib.Path objects in python 3.6 |
|
0 commit comments