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.
- {UncountablePythonSDK-0.0.47.dist-info → UncountablePythonSDK-0.0.49.dist-info}/METADATA +2 -1
- {UncountablePythonSDK-0.0.47.dist-info → UncountablePythonSDK-0.0.49.dist-info}/RECORD +15 -14
- examples/invoke_uploader.py +4 -4
- pkgs/filesystem_utils/__init__.py +2 -0
- pkgs/filesystem_utils/_s3_session.py +116 -0
- pkgs/filesystem_utils/file_type_utils.py +10 -0
- uncountable/integration/executors/generic_upload_executor.py +45 -14
- uncountable/types/api/uploader/invoke_uploader.py +2 -2
- uncountable/types/async_batch_processor.py +5 -5
- uncountable/types/client_base.py +5 -5
- uncountable/types/generic_upload_t.py +0 -1
- uncountable/types/job_definition.py +2 -0
- uncountable/types/job_definition_t.py +26 -1
- {UncountablePythonSDK-0.0.47.dist-info → UncountablePythonSDK-0.0.49.dist-info}/WHEEL +0 -0
- {UncountablePythonSDK-0.0.47.dist-info → UncountablePythonSDK-0.0.49.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: UncountablePythonSDK
|
|
3
|
-
Version: 0.0.
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
127
|
-
uncountable/types/job_definition_t.py,sha256=
|
|
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=
|
|
240
|
-
UncountablePythonSDK-0.0.
|
|
241
|
-
UncountablePythonSDK-0.0.
|
|
242
|
-
UncountablePythonSDK-0.0.
|
|
243
|
-
UncountablePythonSDK-0.0.
|
|
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,,
|
examples/invoke_uploader.py
CHANGED
|
@@ -10,14 +10,14 @@ client = Client(
|
|
|
10
10
|
api_secret_key=os.environ["UNC_API_SECRET_KEY"],
|
|
11
11
|
),
|
|
12
12
|
)
|
|
13
|
-
|
|
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
|
-
|
|
20
|
+
file_id=uploaded_file.file_id,
|
|
21
21
|
uploader_key=IdentifierKeyId(id=48),
|
|
22
|
-
|
|
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
|
-
|
|
269
|
+
for material_family_key in self.upload_strategy.material_family_keys:
|
|
231
270
|
for file_id in file_ids:
|
|
232
|
-
|
|
233
|
-
|
|
271
|
+
batch_processor.invoke_uploader(
|
|
272
|
+
file_id=file_id,
|
|
234
273
|
uploader_key=self.upload_strategy.uploader_key,
|
|
235
|
-
|
|
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
|
-
|
|
29
|
+
file_id: base_t.ObjectId
|
|
30
30
|
uploader_key: identifier_t.IdentifierKey
|
|
31
|
-
|
|
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
|
-
|
|
163
|
+
file_id: base_t.ObjectId,
|
|
164
164
|
uploader_key: identifier_t.IdentifierKey,
|
|
165
|
-
|
|
165
|
+
material_family_key: identifier_t.IdentifierKey,
|
|
166
166
|
depends_on: typing.Optional[list[str]] = None,
|
|
167
167
|
) -> async_batch_t.QueuedAsyncBatchRequest:
|
|
168
|
-
"""Runs
|
|
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
|
-
|
|
173
|
+
file_id=file_id,
|
|
174
174
|
uploader_key=uploader_key,
|
|
175
|
-
|
|
175
|
+
material_family_key=material_family_key,
|
|
176
176
|
)
|
|
177
177
|
json_data = serialize_for_api(args)
|
|
178
178
|
|
uncountable/types/client_base.py
CHANGED
|
@@ -854,17 +854,17 @@ class ClientMethods(ABC):
|
|
|
854
854
|
def invoke_uploader(
|
|
855
855
|
self,
|
|
856
856
|
*,
|
|
857
|
-
|
|
857
|
+
file_id: base_t.ObjectId,
|
|
858
858
|
uploader_key: identifier_t.IdentifierKey,
|
|
859
|
-
|
|
859
|
+
material_family_key: identifier_t.IdentifierKey,
|
|
860
860
|
) -> invoke_uploader_t.Data:
|
|
861
|
-
"""Runs
|
|
861
|
+
"""Runs a file through an uploader.
|
|
862
862
|
|
|
863
863
|
"""
|
|
864
864
|
args = invoke_uploader_t.Arguments(
|
|
865
|
-
|
|
865
|
+
file_id=file_id,
|
|
866
866
|
uploader_key=uploader_key,
|
|
867
|
-
|
|
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
|
]
|
|
File without changes
|
{UncountablePythonSDK-0.0.47.dist-info → UncountablePythonSDK-0.0.49.dist-info}/top_level.txt
RENAMED
|
File without changes
|