UncountablePythonSDK 0.0.47__py3-none-any.whl → 0.0.49__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.

Potentially problematic release.


This version of UncountablePythonSDK might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: UncountablePythonSDK
3
- Version: 0.0.47
3
+ Version: 0.0.49
4
4
  Summary: Uncountable SDK
5
5
  Project-URL: Homepage, https://github.com/uncountableinc/uncountable-python-sdk
6
6
  Project-URL: Repository, https://github.com/uncountableinc/uncountable-python-sdk.git
@@ -28,6 +28,7 @@ Requires-Dist: google-api-python-client ==2.*
28
28
  Requires-Dist: tqdm ==4.*
29
29
  Requires-Dist: pysftp ==0.*
30
30
  Requires-Dist: paramiko ==3.*
31
+ Requires-Dist: boto3 ==1.*
31
32
  Provides-Extra: test
32
33
  Requires-Dist: mypy ==1.* ; extra == 'test'
33
34
  Requires-Dist: ruff ==0.* ; extra == 'test'
@@ -17,7 +17,7 @@ docs/static/favicons/safari-pinned-tab.svg,sha256=S84fRnz0ZxLnQrKtmmFZytiRyu1xLt
17
17
  examples/async_batch.py,sha256=CffQ8O9drJ-Mdd6S5DnMIOBsHv5aVkTZrD3l3xBnB4s,1094
18
18
  examples/create_entity.py,sha256=noZdtJ5f9Wfiob3zUH-8bDVbrCPJnFtXFk_W9pSjvUA,664
19
19
  examples/edit_recipe_inputs.py,sha256=SFHhccktlpWlz4ujh30KZ-hZTFneRWuEp9b3nJSbNn8,1647
20
- examples/invoke_uploader.py,sha256=NOHsvObRGLrOYaSKtwk1xkhsdLEdbUmrseUX6JzC4Q0,635
20
+ examples/invoke_uploader.py,sha256=Rn5sSZowW6_0yX6843q2HOSIECfK4cOCB1Hmco3iWVo,625
21
21
  examples/upload_files.py,sha256=tUfKFqiqwnw08OL5Y8_e4j5pSRhp94cFex8XTuVa_ig,487
22
22
  pkgs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  pkgs/argument_parser/__init__.py,sha256=CsQ6QoPKSLLRVl-z6URAmPkiUL9ZPZoV4rJHgy_-RjA,385
@@ -25,11 +25,12 @@ pkgs/argument_parser/_is_enum.py,sha256=Gw6jJa8nBwYGqXwwCZbSnWL8Rvr5alkg5lSVAqXt
25
25
  pkgs/argument_parser/_is_namedtuple.py,sha256=Rjc1bKanIPPogl3qG5JPBxglG1TqWYOo1nxxhBASQWY,265
26
26
  pkgs/argument_parser/argument_parser.py,sha256=S5x4yDpaBqTRkmcOyX2UuBWw9iCE4j2Po5LZPg9jhe4,17308
27
27
  pkgs/argument_parser/case_convert.py,sha256=NuJLJUJRbyVb6_Slen4uqaStEHbcOS1d-hBBfDrrw-c,605
28
- pkgs/filesystem_utils/__init__.py,sha256=yxfwtYFvqq_fjMl-tg2jwa6eNPNZUF4ykqsSzALyNdw,1143
28
+ pkgs/filesystem_utils/__init__.py,sha256=NSsQrUCoGISBCqCCyq6_583sYHTVEQeDjDO8hvZn3ag,1261
29
29
  pkgs/filesystem_utils/_gdrive_session.py,sha256=OZudNoP2HikolnpurVJhJdh5fgzqbaZQvn53ReGGXx4,11015
30
30
  pkgs/filesystem_utils/_local_session.py,sha256=xFEYhAvNqrOYqwt4jrEYOuYkjJn0zclZhTelW_Q1-rw,2325
31
+ pkgs/filesystem_utils/_s3_session.py,sha256=q4q0MTWXWu5RNRVZ5ibv4M4UXXxWl_J6xCnitvngIMM,3957
31
32
  pkgs/filesystem_utils/_sftp_session.py,sha256=gNoUD_b4MuVqWj31nU-FpfpXZlyWkwdEHtX1S8W6gpQ,4727
32
- pkgs/filesystem_utils/file_type_utils.py,sha256=CC2c7127TE84vlRTy7bpE4Qvy7ytxuRNA3dwP91PXA8,1257
33
+ pkgs/filesystem_utils/file_type_utils.py,sha256=Xd-mg35mAENUgNJVz5uK8nEfrUp-NQld_gnXFEq3K-8,1487
33
34
  pkgs/filesystem_utils/filesystem_session.py,sha256=BQ2Go8Mu9-GcnaWh2Pm4x7ugLVsres6XrOQ8RoiEpcE,1045
34
35
  pkgs/serialization/__init__.py,sha256=LifasRW0a50A3qRFmo2bf3FQ6TXhZWOTz2-CVTgPjcQ,753
35
36
  pkgs/serialization/missing_sentry.py,sha256=aM_9KxbCk9dVvXvcOKgkIQBqFWvLhv8QlIUCiuFEXMo,806
@@ -86,13 +87,13 @@ uncountable/integration/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
86
87
  uncountable/integration/db/connect.py,sha256=YtQHJ1DBGPhxKFRCfiXqohOYUceKSxMVOJ88aPI48Ug,181
87
88
  uncountable/integration/executors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
88
89
  uncountable/integration/executors/executors.py,sha256=v5ClGVUlvrZcMdmGQa8Ll668G_HGTnKpGOnTM7UMZCQ,956
89
- uncountable/integration/executors/generic_upload_executor.py,sha256=axGpIfF0IMHilGzFawDizWoDhYffeqYXuhfyYkQhWF4,8754
90
+ uncountable/integration/executors/generic_upload_executor.py,sha256=HR5bZE1DMVLRB-lwmsfZnoLEsaL66aHZM0xCEO_D3-4,10238
90
91
  uncountable/integration/executors/script_executor.py,sha256=OmSBOtU48G3mqza9c2lCm84pGGyaDk-ZBJCx3RsdJXc,846
91
92
  uncountable/integration/secret_retrieval/__init__.py,sha256=3QXVj35w8rRMxVvmmsViFYDi3lcb3g70incfalOEm6o,87
92
93
  uncountable/integration/secret_retrieval/retrieve_secret.py,sha256=M0qXVJpD8hMYIFypHFeyh598sqmIDX8ZOyXK23CluF0,1323
93
94
  uncountable/types/__init__.py,sha256=95iOd3WXWoI_4a461IS2ieWRic3zRyNaCYzfTpX764o,8162
94
95
  uncountable/types/async_batch.py,sha256=ihCv5XWSTTPmuO-GMPn1EACGI2CBUIJTATZ3aPgsNBA,523
95
- uncountable/types/async_batch_processor.py,sha256=BNEWdRIcuJHGeY4KVCudf4gG-kV6_dZR7aeSWupCPjk,8581
96
+ uncountable/types/async_batch_processor.py,sha256=VO0P-oSLAvzBK_Rk-KPS74AhSyrkj5ILZrAu-gs7Lpc,8564
96
97
  uncountable/types/async_batch_t.py,sha256=9jp9rOyetRdD5aQVyijzQggTyYU4021PBVGXk0ooBCQ,1911
97
98
  uncountable/types/base.py,sha256=xVSjWvA_fUUnkCg83EjoYEFvAfmskinKFMeYFOxNc9E,359
98
99
  uncountable/types/base_t.py,sha256=XXjZXexx0xWFUxMMhW8i9nIL6n8dsZVsHwdgnhZ0zJ4,2714
@@ -100,7 +101,7 @@ uncountable/types/calculations.py,sha256=FFO_D3BbKoGDZnqWvTKpW4KF359i2vrKjpdFCLY
100
101
  uncountable/types/calculations_t.py,sha256=7GTSi2L8NYjzjUJJx3cmtVkK9uD-uhfYvIFK-ffQj-8,556
101
102
  uncountable/types/chemical_structure.py,sha256=E-LnikTFDoVQ1b2zKaVUIO_PAKm-7aZZYJi8I8SDSic,302
102
103
  uncountable/types/chemical_structure_t.py,sha256=aFsTkkbzy6Gvyde3qrrEYD95gcYhxkgKMiDRaRE0o-Y,760
103
- uncountable/types/client_base.py,sha256=foD_tf3-FniOHIDO7AdPpPr5FdIDhUNVceXlyo-pA2M,65257
104
+ uncountable/types/client_base.py,sha256=3CKr_v3bXez7ZjUg41q09pju5tgYGMsbgJAoYMffh-c,65240
104
105
  uncountable/types/client_config.py,sha256=4h5Liko9uKCo9_0gdbPhoK6Jr2Kv7tioLiQ8iKeq-_4,301
105
106
  uncountable/types/client_config_t.py,sha256=_HdS37gMSTIiD4qLnW9dIgt8_Rt5A6xhwMGGga7vnLg,625
106
107
  uncountable/types/curves.py,sha256=W6uMpG5SyW1MS82szNpxkFEn1MnxNpBFyFbQb2Ysfng,366
@@ -114,7 +115,7 @@ uncountable/types/field_values_t.py,sha256=_JYzmHlPczEnROz_tUbY0r3mZWffCB8m2_a9k
114
115
  uncountable/types/fields.py,sha256=GUY5ne8Zp2_Lalikr0zcbdJrin8dG81eyS8fKWJ9yf8,266
115
116
  uncountable/types/fields_t.py,sha256=9pDroeNvj4hQQ4QB4-2ioTAFR30YTuW_364Q-Ka9SoQ,564
116
117
  uncountable/types/generic_upload.py,sha256=hbtuOVMJhCRfPQM_BwtLMV509--iv8NXjtlTfCeRqdU,395
117
- uncountable/types/generic_upload_t.py,sha256=3Sv6oddGthbZXT77x_Soy8CClysAbNZ4McUpoazIKA4,1237
118
+ uncountable/types/generic_upload_t.py,sha256=euOF_oFwT62a---oBjZeGVvU67TpXh8da9njrhlg46w,1202
118
119
  uncountable/types/id_source.py,sha256=wGLA0fMl-5IqBG_fg2UDC7fY-8CWRBNFJOokejOoN_w,567
119
120
  uncountable/types/id_source_t.py,sha256=ReOuIHIuFbkE2mQ58x6UP4jCV4_JUxMCvAU9bxUxG84,1369
120
121
  uncountable/types/identifier.py,sha256=6ziONd__L07ijhVwpIehUUDvxgKTtHbunk-6CivJqxQ,503
@@ -123,8 +124,8 @@ uncountable/types/input_attributes.py,sha256=IrIKQnHqHdS1Utdfzr9GnOe17a8riaqYcO1
123
124
  uncountable/types/input_attributes_t.py,sha256=wE1ekiQfb72Z9VpF5SHipKJkgaJFUHJrNkkJdmuhF9w,820
124
125
  uncountable/types/inputs.py,sha256=6RIEFfCxLqpeHEGOpu63O4i8zPogjGeB7wiV_rPBw_g,404
125
126
  uncountable/types/inputs_t.py,sha256=RW7gF9zTOwByu-nMTMVuBabLOuWKx4O1nvfgvx_R55o,1611
126
- uncountable/types/job_definition.py,sha256=W2wth9vPRBVL0temwnSmvfxxPMKFxZqbQtsMXn8BU3U,1637
127
- uncountable/types/job_definition_t.py,sha256=1VZO3l5oGnvtG-ikQB6YuukblHhEwtmJfQpGmo0Gi40,5735
127
+ uncountable/types/job_definition.py,sha256=sCQqtyHI3hksc5pVpk5tqbG55F91ST4FoDwr2TmTOuQ,1787
128
+ uncountable/types/job_definition_t.py,sha256=DpqTYDocJ6n6kVq5PBFhFb5Dlb2vpHdp9IZfFb1Qcpo,6590
128
129
  uncountable/types/outputs.py,sha256=sUZx_X-TKCZtLm1YCEH8OISX9DdPlv9ZuUfM3-askCc,281
129
130
  uncountable/types/outputs_t.py,sha256=2aORUOr0ls1ZYo-ddkWax3D1ZndmQsWtHfJxpYozlhg,656
130
131
  uncountable/types/permissions.py,sha256=1mRnSsmRgjuLgp6pylTwwACD_YRIcmlqxHkufwZtMns,297
@@ -236,8 +237,8 @@ uncountable/types/api/recipes/unlock_recipes.py,sha256=AvzQeZCLs9i7CuhMs3Xltdi4n
236
237
  uncountable/types/api/triggers/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
237
238
  uncountable/types/api/triggers/run_trigger.py,sha256=_Rpha9nxXI3Xr17CrGDtofg4HZ81x2lt0rMZ6As0qfE,893
238
239
  uncountable/types/api/uploader/__init__.py,sha256=gCgbynxG3jA8FQHzercKtrHKHkiIKr8APdZYUniAor8,55
239
- uncountable/types/api/uploader/invoke_uploader.py,sha256=2RCvnccwEm6J5dt7UJVLUKxfk49vfpGXjBLCWIBD3Ck,1006
240
- UncountablePythonSDK-0.0.47.dist-info/METADATA,sha256=GNA-6K_SnpEbHAp-TfhxvSwjxMjceKun1_M3twl0aW8,1707
241
- UncountablePythonSDK-0.0.47.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
242
- UncountablePythonSDK-0.0.47.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
243
- UncountablePythonSDK-0.0.47.dist-info/RECORD,,
240
+ uncountable/types/api/uploader/invoke_uploader.py,sha256=w7NVSpTpKxsF2TgLzVrji1Ql5Z8QWTz6d5OvXpRtyDo,992
241
+ UncountablePythonSDK-0.0.49.dist-info/METADATA,sha256=YzHISP7OjFmFRJWuVPWl6jgdlvsE6W7glCma0mhU7sc,1734
242
+ UncountablePythonSDK-0.0.49.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
243
+ UncountablePythonSDK-0.0.49.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
244
+ UncountablePythonSDK-0.0.49.dist-info/RECORD,,
@@ -10,14 +10,14 @@ client = Client(
10
10
  api_secret_key=os.environ["UNC_API_SECRET_KEY"],
11
11
  ),
12
12
  )
13
- uploaded = client.upload_files(
13
+ uploaded_file = client.upload_files(
14
14
  file_uploads=[
15
15
  MediaFileUpload(path="~/Downloads/my_file_to_upload.csv"),
16
16
  ]
17
- )
17
+ )[0]
18
18
 
19
19
  client.invoke_uploader(
20
- file_ids=[file.file_id for file in uploaded],
20
+ file_id=uploaded_file.file_id,
21
21
  uploader_key=IdentifierKeyId(id=48),
22
- material_family_keys=[IdentifierKeyId(id=1)],
22
+ material_family_key=IdentifierKeyId(id=1),
23
23
  )
@@ -5,12 +5,14 @@ from ._gdrive_session import list_gdrive_files as list_gdrive_files
5
5
  from ._gdrive_session import move_gdrive_file as move_gdrive_file
6
6
  from ._gdrive_session import upload_file_gdrive as upload_file_gdrive
7
7
  from ._local_session import LocalSession as LocalSession
8
+ from ._s3_session import S3Session as S3Session
8
9
  from ._sftp_session import SFTPSession as SFTPSession
9
10
  from ._sftp_session import list_sftp_files as list_sftp_files
10
11
  from ._sftp_session import move_sftp_files as move_sftp_files
11
12
  from .file_type_utils import FileObjectData as FileObjectData
12
13
  from .file_type_utils import FileSystemFileReference as FileSystemFileReference
13
14
  from .file_type_utils import FileSystemObject as FileSystemObject
15
+ from .file_type_utils import FileSystemS3Config as FileSystemS3Config
14
16
  from .file_type_utils import FileSystemSFTPConfig as FileSystemSFTPConfig
15
17
  from .file_type_utils import FileTransfer as FileTransfer
16
18
  from .file_type_utils import IncompatibleFileReference as IncompatibleFileReference
@@ -0,0 +1,116 @@
1
+ from io import BytesIO
2
+
3
+ from boto3.session import Session
4
+
5
+ from pkgs.filesystem_utils.file_type_utils import (
6
+ FileObjectData,
7
+ FileSystemFileReference,
8
+ FileSystemObject,
9
+ FileSystemS3Config,
10
+ FileTransfer,
11
+ IncompatibleFileReference,
12
+ )
13
+
14
+ from .filesystem_session import FileSystemSession
15
+
16
+
17
+ def _add_slash(prefix: str) -> str:
18
+ if len(prefix) > 0 and prefix[-1] != "/":
19
+ prefix = prefix + "/"
20
+ return prefix
21
+
22
+
23
+ class S3Session(FileSystemSession):
24
+ config: FileSystemS3Config
25
+
26
+ def __init__(self, s3_config: FileSystemS3Config) -> None:
27
+ super().__init__()
28
+ self.config = s3_config
29
+
30
+ def start(self) -> None:
31
+ session = Session(region_name=self.config.region_name)
32
+ s3_resource = session.resource(
33
+ service_name="s3",
34
+ endpoint_url=self.config.endpoint_url,
35
+ aws_access_key_id=self.config.access_key_id,
36
+ aws_secret_access_key=self.config.secret_access_key,
37
+ aws_session_token=self.config.session_token,
38
+ )
39
+
40
+ self.bucket = s3_resource.Bucket(self.config.bucket_name)
41
+
42
+ def __enter__(self) -> "S3Session":
43
+ self.start()
44
+ return self
45
+
46
+ def __exit__(self, exc_type: object, exc_val: object, exc_tb: object) -> None:
47
+ self.bucket = None
48
+
49
+ def list_files(
50
+ self,
51
+ dir_path: FileSystemObject,
52
+ *,
53
+ recursive: bool = False,
54
+ valid_extensions: list[str] | None = None,
55
+ ) -> list[FileSystemObject]:
56
+ if recursive:
57
+ raise NotImplementedError("recursive file listings not implemented for s3")
58
+
59
+ if not isinstance(dir_path, FileSystemFileReference):
60
+ raise IncompatibleFileReference()
61
+
62
+ assert self.bucket is not None, "call to list_files on uninitialized s3 session"
63
+
64
+ filesystem_references: list[FileSystemObject] = []
65
+ for obj in self.bucket.objects.filter(Prefix=dir_path.filepath):
66
+ if valid_extensions is None or any(
67
+ obj.key.endswith(valid_extension) for valid_extension in valid_extensions
68
+ ):
69
+ filesystem_references.append(FileSystemFileReference(obj.key))
70
+
71
+ return filesystem_references
72
+
73
+ def download_files(
74
+ self,
75
+ filepaths: list[FileSystemObject],
76
+ ) -> list[FileObjectData]:
77
+ downloaded_files: list[FileObjectData] = []
78
+ assert (
79
+ self.bucket is not None
80
+ ), "call to download_files on uninitialized s3 session"
81
+
82
+ for file_object in filepaths:
83
+ if (
84
+ not isinstance(file_object, FileSystemFileReference)
85
+ or file_object.filename is None
86
+ ):
87
+ raise IncompatibleFileReference()
88
+ s3_file_obj = self.bucket.Object(_add_slash(file_object.filepath))
89
+ response = s3_file_obj.get()
90
+ file_obj_bytes = response["Body"].read()
91
+ downloaded_files.append(
92
+ FileObjectData(
93
+ file_data=file_obj_bytes,
94
+ file_IO=BytesIO(file_obj_bytes),
95
+ filename=file_object.filename,
96
+ filepath=file_object.filepath,
97
+ )
98
+ )
99
+
100
+ return downloaded_files
101
+
102
+ def move_files(self, file_mappings: list[FileTransfer]) -> None:
103
+ assert self.bucket is not None, "call to move_files on uninitialized s3 session"
104
+
105
+ for src_file, dest_file in file_mappings:
106
+ if not isinstance(src_file, FileSystemFileReference) or not isinstance(
107
+ dest_file, FileSystemFileReference
108
+ ):
109
+ raise IncompatibleFileReference()
110
+ self.bucket.Object(_add_slash(dest_file.filepath)).copy_from(
111
+ CopySource={
112
+ "Bucket": self.bucket.name,
113
+ "Key": _add_slash(src_file.filepath),
114
+ }
115
+ )
116
+ self.bucket.Object(_add_slash(src_file.filepath)).delete()
@@ -59,3 +59,13 @@ class FileSystemSFTPConfig:
59
59
  password: str | None = None
60
60
  valid_extensions: Optional[tuple[str]] = None
61
61
  recursive: bool = True
62
+
63
+
64
+ @dataclass(kw_only=True)
65
+ class FileSystemS3Config:
66
+ endpoint_url: str
67
+ bucket_name: str
68
+ region_name: Optional[str]
69
+ access_key_id: Optional[str]
70
+ secret_access_key: Optional[str]
71
+ session_token: Optional[str]
@@ -9,12 +9,13 @@ from pkgs.filesystem_utils import (
9
9
  FileObjectData,
10
10
  FileSystemFileReference,
11
11
  FileSystemObject,
12
+ FileSystemS3Config,
12
13
  FileSystemSFTPConfig,
13
14
  FileTransfer,
15
+ S3Session,
14
16
  SFTPSession,
15
17
  )
16
18
  from pkgs.filesystem_utils.filesystem_session import FileSystemSession
17
- from uncountable.core.async_batch import AsyncBatchProcessor
18
19
  from uncountable.core.file_upload import DataFileUpload, FileUpload
19
20
  from uncountable.integration.job import Job, JobArguments, JobLogger
20
21
  from uncountable.integration.secret_retrieval import retrieve_secret
@@ -24,8 +25,10 @@ from uncountable.types.generic_upload_t import (
24
25
  )
25
26
  from uncountable.types.job_definition_t import (
26
27
  GenericUploadDataSource,
28
+ GenericUploadDataSourceS3,
27
29
  GenericUploadDataSourceSFTP,
28
30
  JobResult,
31
+ S3CloudProvider,
29
32
  )
30
33
 
31
34
 
@@ -186,12 +189,48 @@ class GenericUploadJob(Job):
186
189
  pem_key=pem_key,
187
190
  )
188
191
  return SFTPSession(sftp_config=sftp_config)
192
+ case GenericUploadDataSourceS3():
193
+ if self.data_source.access_key_secret is not None:
194
+ secret_access_key = retrieve_secret(
195
+ self.data_source.access_key_secret,
196
+ profile_metadata=args.profile_metadata,
197
+ )
198
+ else:
199
+ secret_access_key = None
200
+
201
+ if self.data_source.endpoint_url is None:
202
+ assert (
203
+ self.data_source.cloud_provider is not None
204
+ ), "either cloud_provider or endpoint_url must be specified"
205
+ match self.data_source.cloud_provider:
206
+ case S3CloudProvider.AWS:
207
+ endpoint_url = "https://s3.amazonaws.com"
208
+ case S3CloudProvider.OVH:
209
+ assert (
210
+ self.data_source.region_name is not None
211
+ ), "region_name must be specified for cloud_provider OVH"
212
+ endpoint_url = (
213
+ f"https://s3.{self.data_source.region_name}.cloud.ovh.net"
214
+ )
215
+ else:
216
+ endpoint_url = self.data_source.endpoint_url
217
+
218
+ s3_config = FileSystemS3Config(
219
+ endpoint_url=endpoint_url,
220
+ bucket_name=self.data_source.bucket_name,
221
+ region_name=self.data_source.region_name,
222
+ access_key_id=self.data_source.access_key_id,
223
+ secret_access_key=secret_access_key,
224
+ session_token=None,
225
+ )
226
+
227
+ return S3Session(s3_config=s3_config)
189
228
 
190
229
  def run(self, args: JobArguments) -> JobResult:
191
230
  client = args.client
231
+ batch_processor = args.batch_processor
192
232
  logger = args.logger
193
233
 
194
- batch_executor = AsyncBatchProcessor(client=client)
195
234
  with self._construct_filesystem_session(args) as filesystem_session:
196
235
  files_to_upload: list[FileUpload] = []
197
236
  for remote_directory in self.remote_directories:
@@ -227,20 +266,12 @@ class GenericUploadJob(Job):
227
266
 
228
267
  file_ids = [file.file_id for file in uploaded_files]
229
268
 
230
- if self.upload_strategy.parse_files_individually:
269
+ for material_family_key in self.upload_strategy.material_family_keys:
231
270
  for file_id in file_ids:
232
- batch_executor.invoke_uploader(
233
- file_ids=[file_id],
271
+ batch_processor.invoke_uploader(
272
+ file_id=file_id,
234
273
  uploader_key=self.upload_strategy.uploader_key,
235
- material_family_keys=self.upload_strategy.material_family_keys,
274
+ material_family_key=material_family_key,
236
275
  )
237
- else:
238
- batch_executor.invoke_uploader(
239
- file_ids=file_ids,
240
- uploader_key=self.upload_strategy.uploader_key,
241
- material_family_keys=self.upload_strategy.material_family_keys,
242
- )
243
-
244
- batch_executor.send()
245
276
 
246
277
  return JobResult(success=True)
@@ -26,9 +26,9 @@ ENDPOINT_PATH = "api/external/uploader/invoke_uploader"
26
26
  # DO NOT MODIFY -- This file is generated by type_spec
27
27
  @dataclasses.dataclass(kw_only=True)
28
28
  class Arguments:
29
- file_ids: list[base_t.ObjectId]
29
+ file_id: base_t.ObjectId
30
30
  uploader_key: identifier_t.IdentifierKey
31
- material_family_keys: list[identifier_t.IdentifierKey]
31
+ material_family_key: identifier_t.IdentifierKey
32
32
 
33
33
 
34
34
  # DO NOT MODIFY -- This file is generated by type_spec
@@ -160,19 +160,19 @@ class AsyncBatchProcessorBase(ABC):
160
160
  def invoke_uploader(
161
161
  self,
162
162
  *,
163
- file_ids: list[base_t.ObjectId],
163
+ file_id: base_t.ObjectId,
164
164
  uploader_key: identifier_t.IdentifierKey,
165
- material_family_keys: list[identifier_t.IdentifierKey],
165
+ material_family_key: identifier_t.IdentifierKey,
166
166
  depends_on: typing.Optional[list[str]] = None,
167
167
  ) -> async_batch_t.QueuedAsyncBatchRequest:
168
- """Runs files through an uploader.
168
+ """Runs a file through an uploader.
169
169
 
170
170
  :param depends_on: A list of batch reference keys to process before processing this request
171
171
  """
172
172
  args = invoke_uploader_t.Arguments(
173
- file_ids=file_ids,
173
+ file_id=file_id,
174
174
  uploader_key=uploader_key,
175
- material_family_keys=material_family_keys,
175
+ material_family_key=material_family_key,
176
176
  )
177
177
  json_data = serialize_for_api(args)
178
178
 
@@ -854,17 +854,17 @@ class ClientMethods(ABC):
854
854
  def invoke_uploader(
855
855
  self,
856
856
  *,
857
- file_ids: list[base_t.ObjectId],
857
+ file_id: base_t.ObjectId,
858
858
  uploader_key: identifier_t.IdentifierKey,
859
- material_family_keys: list[identifier_t.IdentifierKey],
859
+ material_family_key: identifier_t.IdentifierKey,
860
860
  ) -> invoke_uploader_t.Data:
861
- """Runs files through an uploader.
861
+ """Runs a file through an uploader.
862
862
 
863
863
  """
864
864
  args = invoke_uploader_t.Arguments(
865
- file_ids=file_ids,
865
+ file_id=file_id,
866
866
  uploader_key=uploader_key,
867
- material_family_keys=material_family_keys,
867
+ material_family_key=material_family_key,
868
868
  )
869
869
  api_request = APIRequest(
870
870
  method=invoke_uploader_t.ENDPOINT_METHOD,
@@ -36,6 +36,5 @@ class GenericRemoteDirectoryScope:
36
36
  class GenericUploadStrategy:
37
37
  uploader_key: identifier_t.IdentifierKey
38
38
  material_family_keys: list[identifier_t.IdentifierKey]
39
- parse_files_individually: bool
40
39
  skip_moving_files: bool = False
41
40
  # DO NOT MODIFY -- This file is generated by type_spec
@@ -12,6 +12,8 @@ from .job_definition_t import JobExecutorBase as JobExecutorBase
12
12
  from .job_definition_t import JobExecutorScript as JobExecutorScript
13
13
  from .job_definition_t import GenericUploadDataSourceBase as GenericUploadDataSourceBase
14
14
  from .job_definition_t import GenericUploadDataSourceSFTP as GenericUploadDataSourceSFTP
15
+ from .job_definition_t import S3CloudProvider as S3CloudProvider
16
+ from .job_definition_t import GenericUploadDataSourceS3 as GenericUploadDataSourceS3
15
17
  from .job_definition_t import GenericUploadDataSource as GenericUploadDataSource
16
18
  from .job_definition_t import JobExecutorGenericUpload as JobExecutorGenericUpload
17
19
  from .job_definition_t import JobExecutor as JobExecutor
@@ -23,6 +23,7 @@ __all__: list[str] = [
23
23
  "CronJobDefinition",
24
24
  "GenericUploadDataSource",
25
25
  "GenericUploadDataSourceBase",
26
+ "GenericUploadDataSourceS3",
26
27
  "GenericUploadDataSourceSFTP",
27
28
  "GenericUploadDataSourceType",
28
29
  "JobDefinition",
@@ -36,6 +37,7 @@ __all__: list[str] = [
36
37
  "JobResult",
37
38
  "ProfileDefinition",
38
39
  "ProfileMetadata",
40
+ "S3CloudProvider",
39
41
  ]
40
42
 
41
43
 
@@ -58,6 +60,7 @@ class AuthRetrievalType(StrEnum):
58
60
  # DO NOT MODIFY -- This file is generated by type_spec
59
61
  class GenericUploadDataSourceType(StrEnum):
60
62
  SFTP = "sftp"
63
+ S3 = "s3"
61
64
 
62
65
 
63
66
  # DO NOT MODIFY -- This file is generated by type_spec
@@ -94,13 +97,35 @@ class GenericUploadDataSourceSFTP(GenericUploadDataSourceBase):
94
97
  pem_secret: secret_retrieval_t.SecretRetrieval
95
98
 
96
99
 
100
+ # DO NOT MODIFY -- This file is generated by type_spec
101
+ class S3CloudProvider(StrEnum):
102
+ OVH = "ovh"
103
+ AWS = "aws"
104
+
105
+
106
+ # DO NOT MODIFY -- This file is generated by type_spec
107
+ @serial_class(
108
+ parse_require={"type"},
109
+ )
110
+ @dataclasses.dataclass(kw_only=True)
111
+ class GenericUploadDataSourceS3(GenericUploadDataSourceBase):
112
+ type: typing.Literal[GenericUploadDataSourceType.S3] = GenericUploadDataSourceType.S3
113
+ bucket_name: str
114
+ cloud_provider: typing.Optional[S3CloudProvider] = None
115
+ endpoint_url: typing.Optional[str] = None
116
+ region_name: typing.Optional[str] = None
117
+ access_key_id: typing.Optional[str] = None
118
+ access_key_secret: typing.Optional[secret_retrieval_t.SecretRetrieval] = None
119
+
120
+
97
121
  # DO NOT MODIFY -- This file is generated by type_spec
98
122
  GenericUploadDataSource = typing.Annotated[
99
- typing.Union[GenericUploadDataSourceSFTP],
123
+ typing.Union[GenericUploadDataSourceSFTP, GenericUploadDataSourceS3],
100
124
  serial_union_annotation(
101
125
  discriminator="type",
102
126
  discriminator_map={
103
127
  "sftp": GenericUploadDataSourceSFTP,
128
+ "s3": GenericUploadDataSourceS3,
104
129
  },
105
130
  ),
106
131
  ]