UncountablePythonSDK 0.0.47__py3-none-any.whl → 0.0.48__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.48
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
@@ -28,8 +28,9 @@ pkgs/argument_parser/case_convert.py,sha256=NuJLJUJRbyVb6_Slen4uqaStEHbcOS1d-hBB
28
28
  pkgs/filesystem_utils/__init__.py,sha256=yxfwtYFvqq_fjMl-tg2jwa6eNPNZUF4ykqsSzALyNdw,1143
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=Ixxg01LLzc9pBpptBqJG6-jG82eltZTF7igFqORujyo,8393
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
@@ -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.48.dist-info/METADATA,sha256=j72rX6737WefzshiwZNBrM8nqCM55gDs2DH6dH79C24,1734
242
+ UncountablePythonSDK-0.0.48.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
243
+ UncountablePythonSDK-0.0.48.dist-info/top_level.txt,sha256=1UVGjAU-6hJY9qw2iJ7nCBeEwZ793AEN5ZfKX9A1uj4,31
244
+ UncountablePythonSDK-0.0.48.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
  )
@@ -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]
@@ -14,7 +14,6 @@ from pkgs.filesystem_utils import (
14
14
  SFTPSession,
15
15
  )
16
16
  from pkgs.filesystem_utils.filesystem_session import FileSystemSession
17
- from uncountable.core.async_batch import AsyncBatchProcessor
18
17
  from uncountable.core.file_upload import DataFileUpload, FileUpload
19
18
  from uncountable.integration.job import Job, JobArguments, JobLogger
20
19
  from uncountable.integration.secret_retrieval import retrieve_secret
@@ -189,9 +188,9 @@ class GenericUploadJob(Job):
189
188
 
190
189
  def run(self, args: JobArguments) -> JobResult:
191
190
  client = args.client
191
+ batch_processor = args.batch_processor
192
192
  logger = args.logger
193
193
 
194
- batch_executor = AsyncBatchProcessor(client=client)
195
194
  with self._construct_filesystem_session(args) as filesystem_session:
196
195
  files_to_upload: list[FileUpload] = []
197
196
  for remote_directory in self.remote_directories:
@@ -227,20 +226,12 @@ class GenericUploadJob(Job):
227
226
 
228
227
  file_ids = [file.file_id for file in uploaded_files]
229
228
 
230
- if self.upload_strategy.parse_files_individually:
229
+ for material_family_key in self.upload_strategy.material_family_keys:
231
230
  for file_id in file_ids:
232
- batch_executor.invoke_uploader(
233
- file_ids=[file_id],
231
+ batch_processor.invoke_uploader(
232
+ file_id=file_id,
234
233
  uploader_key=self.upload_strategy.uploader_key,
235
- material_family_keys=self.upload_strategy.material_family_keys,
234
+ material_family_key=material_family_key,
236
235
  )
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
236
 
246
237
  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