sws-api-client 2.22.2__tar.gz → 2.23.0__tar.gz

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.
Files changed (30) hide show
  1. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/PKG-INFO +1 -1
  2. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/pyproject.toml +1 -1
  3. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/__init__.py +1 -1
  4. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/files.py +93 -6
  5. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/.gitignore +0 -0
  6. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/LICENSE +0 -0
  7. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/README.md +0 -0
  8. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/auth.py +0 -0
  9. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/campaign_manager.py +0 -0
  10. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/case_agnostic_model.py +0 -0
  11. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/codelist.py +0 -0
  12. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/data_retrieval.py +0 -0
  13. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/datasets.py +0 -0
  14. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/datatable.py +0 -0
  15. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/db.py +0 -0
  16. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/discover.py +0 -0
  17. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/flaglist.py +0 -0
  18. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/generic_models.py +0 -0
  19. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/harvester.py +0 -0
  20. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/mail.py +0 -0
  21. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/metadata_instances.py +0 -0
  22. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/plugins.py +0 -0
  23. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/s3.py +0 -0
  24. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/schemas/validation-report.schema.json +0 -0
  25. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/sessions.py +0 -0
  26. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/sws_api_client.py +0 -0
  27. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/tags.py +0 -0
  28. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/tasks.py +0 -0
  29. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/token.py +0 -0
  30. {sws_api_client-2.22.2 → sws_api_client-2.23.0}/sws_api_client/validation_report.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sws_api_client
3
- Version: 2.22.2
3
+ Version: 2.23.0
4
4
  Author-email: Matteo <matteo.terrinoni@fao.org>, Daniele <daniele.mansillo@fao.org>
5
5
  License-File: LICENSE
6
6
  Requires-Python: <4,>=3.10
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sws_api_client"
3
- version = "2.22.2"
3
+ version = "2.23.0"
4
4
  description = ""
5
5
  authors = [
6
6
  { name = "Matteo", email = "matteo.terrinoni@fao.org" },
@@ -57,4 +57,4 @@ from .tags import Tags
57
57
  from .tasks import PluginPayload, TaskDataset, TaskInfo, TaskManager, TaskResponse
58
58
  from .harvester import Harvester
59
59
 
60
- __version__ = "2.22.2"
60
+ __version__ = "2.23.0"
@@ -25,10 +25,10 @@ import os
25
25
  import warnings
26
26
  from datetime import datetime
27
27
  from pathlib import Path
28
- from typing import Dict, List, Literal, Optional, Union
28
+ from typing import Literal
29
29
 
30
30
  import requests
31
- from pydantic import BaseModel
31
+ from pydantic import ValidationError
32
32
 
33
33
  from sws_api_client.case_agnostic_model import CaseAgnosticModel
34
34
  from sws_api_client.sws_api_client import SwsApiClient
@@ -39,6 +39,31 @@ logger = logging.getLogger(__name__)
39
39
  FileObjectType = Literal["domain", "dataset", "datatable"]
40
40
 
41
41
 
42
+ DEFAULT_MIME = "application/octet-stream"
43
+ MIME_EXTENSIONS_LOOKUP = {
44
+ "txt": "text/plain",
45
+ "csv": "text/csv",
46
+ "json": "application/json",
47
+ "xml": "application/xml",
48
+ "zip": "application/zip",
49
+ "pdf": "application/pdf",
50
+ "xls": "application/vnd.ms-excel",
51
+ "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
52
+ "doc": "application/msword",
53
+ "docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
54
+ "py": "text/x-python",
55
+ "r": "text/x-r",
56
+ "md": "text/markdown",
57
+ "ppt": "application/vnd.ms-powerpoint",
58
+ "pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
59
+ "png": "image/png",
60
+ "jpg": "image/jpeg",
61
+ "jpeg": "image/jpeg",
62
+ "gif": "image/gif",
63
+ "svg": "image/svg+xml",
64
+ }
65
+
66
+
42
67
  class CreatorInfo(CaseAgnosticModel):
43
68
  email: str | None = None
44
69
  username: str | None = None
@@ -672,7 +697,9 @@ class Files:
672
697
 
673
698
  # Check if local_path is a directory or ends with /
674
699
  # If so, derive filename from remote path
675
- if str(local_path).endswith("/") or (local_path_obj.exists() and local_path_obj.is_dir()):
700
+ if str(local_path).endswith("/") or (
701
+ local_path_obj.exists() and local_path_obj.is_dir()
702
+ ):
676
703
  remote_filename = os.path.basename(path)
677
704
  local_path_obj = local_path_obj / remote_filename
678
705
 
@@ -681,13 +708,17 @@ class Files:
681
708
  local_path_obj.parent.mkdir(parents=True, exist_ok=True)
682
709
 
683
710
  # Download content from presigned URL and save to file
684
- logger.debug(f"Downloading file from {signed_response.url} to {local_path_obj}")
711
+ logger.debug(
712
+ f"Downloading file from {signed_response.url} to {local_path_obj}"
713
+ )
685
714
  resp = requests.get(signed_response.url)
686
715
  resp.raise_for_status()
687
716
 
688
717
  with open(local_path_obj, "wb") as f:
689
718
  f.write(resp.content)
690
- logger.info(f"Downloaded '{path}' -> {local_path_obj} ({len(resp.content)} bytes)")
719
+ logger.info(
720
+ f"Downloaded '{path}' -> {local_path_obj} ({len(resp.content)} bytes)"
721
+ )
691
722
 
692
723
  return signed_response
693
724
 
@@ -708,6 +739,63 @@ class Files:
708
739
  response = self.sws_client.discoverable.post("session_api", url, data=body)
709
740
  return SignedUploadUrlResponse(**response)
710
741
 
742
+ def _extract_file_extension(self, path: Path) -> str:
743
+ return "".join([c.lstrip(".") for c in path.suffixes])
744
+
745
+ def _get_content_type_from_file_extension(self, extension: str) -> str:
746
+ if extension not in MIME_EXTENSIONS_LOOKUP:
747
+ logger.warning(
748
+ "Unable to identify the content type for a file with extension %s. Defaulting to %s",
749
+ extension,
750
+ DEFAULT_MIME,
751
+ )
752
+ return MIME_EXTENSIONS_LOOKUP.get(extension, DEFAULT_MIME)
753
+
754
+ def _file_exists(
755
+ self, object_type: FileObjectType, object_id: str, *, path: str
756
+ ) -> bool:
757
+ try:
758
+ response = self.list(
759
+ object_type=object_type, object_id=object_id, path=path
760
+ )
761
+ return path in [f.path for f in response.files]
762
+ except ValidationError:
763
+ logger.error("Unexpected response")
764
+ return False
765
+
766
+ def upload_file(
767
+ self,
768
+ object_type: FileObjectType,
769
+ object_id: str,
770
+ *,
771
+ path: str,
772
+ local_path: str,
773
+ new_version: bool = False,
774
+ ) -> SignedUploadUrlResponse:
775
+ extension = self._extract_file_extension(Path(local_path))
776
+ content_type = self._get_content_type_from_file_extension(extension)
777
+ if not new_version:
778
+ file_exists = self._file_exists(
779
+ object_type=object_type, object_id=object_id, path=path
780
+ )
781
+ if file_exists:
782
+ raise FileExistsError(f"File already exists at {path}")
783
+ upload_response = self.upload_url(
784
+ object_type=object_type,
785
+ object_id=object_id,
786
+ path=path,
787
+ content_type=content_type,
788
+ )
789
+ with open(local_path, "rb") as f:
790
+ put_response = requests.put(
791
+ upload_response.url,
792
+ data=f,
793
+ headers=upload_response.requiredHeaders or {},
794
+ )
795
+ put_response.raise_for_status()
796
+ logger.info("Uploaded file %s at %s", local_path, path)
797
+ return upload_response
798
+
711
799
  def create_folder_url(
712
800
  self,
713
801
  object_type: FileObjectType,
@@ -778,7 +866,6 @@ class Files:
778
866
  """Delete a specific S3 object version."""
779
867
  url = f"/files/{object_type}/{object_id}/versions"
780
868
  body = {"path": path, "versionId": version_id}
781
-
782
869
  response = self.sws_client.discoverable.delete("session_api", url, data=body)
783
870
  return DeleteVersionResponse(**response)
784
871
 
File without changes