pilot-platform-object-storage 2.0.0__tar.gz → 2.1.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 (21) hide show
  1. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/PKG-INFO +1 -1
  2. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/object_storage/factories.py +1 -1
  3. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/object_storage/providers/gcp/object_client.py +50 -4
  4. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/pyproject.toml +1 -1
  5. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/README.md +0 -0
  6. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/object_storage/__init__.py +0 -0
  7. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/object_storage/clients/__init__.py +0 -0
  8. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/object_storage/clients/azure_blob_client.py +0 -0
  9. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/object_storage/clients/azure_container_client.py +0 -0
  10. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/object_storage/clients/base_container_client.py +0 -0
  11. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/object_storage/clients/base_file_client.py +0 -0
  12. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/object_storage/config.py +0 -0
  13. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/object_storage/managers/__init__.py +0 -0
  14. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/object_storage/managers/azure_blob_manager.py +0 -0
  15. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/object_storage/managers/base_manager.py +0 -0
  16. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/object_storage/providers/__init__.py +0 -0
  17. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/object_storage/providers/azure.py +0 -0
  18. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/object_storage/providers/enum.py +0 -0
  19. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/object_storage/providers/gcp/__init__.py +0 -0
  20. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/object_storage/providers/gcp/bucket_client.py +0 -0
  21. {pilot_platform_object_storage-2.0.0 → pilot_platform_object_storage-2.1.0}/object_storage/providers/gcp/model.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pilot-platform-object-storage
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: Python library for interacting with multiple object storage APIs.
5
5
  License: Proprietary
6
6
  Author: Indoc Systems
@@ -17,7 +17,7 @@ def get_file_client(provider: Provider | str, sas_url: str, **kwargs: dict[str,
17
17
  if provider == 'azure':
18
18
  return AzureBlobClient(blob_sas_url=sas_url)
19
19
  else:
20
- raise ValueError(f"Unknown provider: {provider}")
20
+ raise ValueError(f'Unknown provider: {provider}')
21
21
 
22
22
 
23
23
  def get_gcs_object_client(project_id: str, service_json: dict[str, Any]) -> GCSObjectClient:
@@ -71,7 +71,7 @@ class GCSObjectClient:
71
71
 
72
72
  def _part_object_name(self, final_key: str, upload_id: str, chunk_number: int) -> str:
73
73
  # Temporary object path for each chunk in google starts with 'multipart/'
74
- return f"multipart/{upload_id}/{final_key}/part-{chunk_number:06d}"
74
+ return f'multipart/{upload_id}/{final_key}/part-{chunk_number:06d}'
75
75
 
76
76
  # Object related methods
77
77
 
@@ -167,6 +167,52 @@ class GCSObjectClient:
167
167
 
168
168
  return session_uri
169
169
 
170
+ async def cancel_resumable_upload(self, session_uri: str) -> None:
171
+ """
172
+ Summary:
173
+ Cancel a Google Cloud Storage resumable upload session. After cancellation,
174
+ the session URI becomes unusable and the upload cannot be resumed or finalized.
175
+
176
+ Behavior:
177
+ - Sends a DELETE to the session URI with Content-Length: 0 (required by GCS JSON API).
178
+ - A successful cancellation returns HTTP 499 (Client Closed Request).
179
+ - Some environments may return 204 (No Content) instead — also valid.
180
+ - If a session is already invalid, GCS may return 404 or 410; these are treated as success.
181
+ - Does NOT delete any uploaded draft chunks or create a final object, because the object
182
+ is only committed at the final POST request.
183
+
184
+ Parameters:
185
+ session_uri (str):
186
+ The resumable upload session URL returned from `get_resumable_session_uri`.
187
+ This is also the value returned by GCS in the `Location` header on upload init.
188
+
189
+ Returns:
190
+ None
191
+
192
+ Raises:
193
+ RuntimeError:
194
+ If the DELETE request returns a status code outside the expected success cases.
195
+
196
+ References:
197
+ https://cloud.google.com/storage/docs/performing-resumable-uploads#cancel-upload
198
+ """
199
+
200
+ def _cancel() -> None:
201
+ authed = self._authed()
202
+ headers = {'Content-Length': '0'}
203
+
204
+ resp = authed.delete(session_uri, headers=headers)
205
+
206
+ # Valid successful outcomes:
207
+ # 499 - documented success status for cancelled GCS resumable uploads
208
+ # 204 - alternative valid success response
209
+ # 404/410 - session already invalid/expired/completed (safe to treat as success)
210
+ success_statuses = (499, 204, 404, 410)
211
+ if resp.status_code not in success_statuses:
212
+ raise RuntimeError(f'Failed to cancel resumable upload: {resp.status_code} {resp.text}')
213
+
214
+ await asyncio.to_thread(_cancel)
215
+
170
216
  async def get_presigned_download_url(
171
217
  self,
172
218
  bucket: str,
@@ -209,7 +255,7 @@ class GCSObjectClient:
209
255
  """
210
256
 
211
257
  def _list() -> dict[int, int]:
212
- prefix = f"multipart/{upload_id}/{final_key}/"
258
+ prefix = f'multipart/{upload_id}/{final_key}/'
213
259
  out: dict[int, int] = {}
214
260
  for b in self._client.list_blobs(bucket, prefix=prefix):
215
261
  leaf = b.name.rsplit('/', 1)[-1] # e.g., part-000037
@@ -318,7 +364,7 @@ class GCSObjectClient:
318
364
  bucket_ref = self._get_bucket(bucket)
319
365
  soft_delete_versions = list(self._client.list_blobs(bucket_ref, prefix=object_name, soft_deleted=True))
320
366
  if not soft_delete_versions:
321
- raise VersionNotFound(f"No soft-deleted versions found for {object_name!r}")
367
+ raise VersionNotFound(f'No soft-deleted versions found for {object_name!r}')
322
368
  soft_delete_versions.sort(key=lambda b: (getattr(b, 'soft_delete_time', None), b.generation), reverse=True)
323
369
  latest = soft_delete_versions[0]
324
370
  gen = int(latest.generation)
@@ -331,7 +377,7 @@ class GCSObjectClient:
331
377
  )
332
378
  resp = authed.post(url, headers={'Content-Type': 'application/json'})
333
379
  if resp.status_code >= 400:
334
- raise RuntimeError(f"Restore failed: {resp.status_code} {resp.text}")
380
+ raise RuntimeError(f'Restore failed: {resp.status_code} {resp.text}')
335
381
 
336
382
  live_blobs = list(self._client.list_blobs(bucket, match_glob=object_name))
337
383
  live_blobs.sort(key=lambda b: (b.time_created, b.generation), reverse=True)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pilot-platform-object-storage"
3
- version = "2.0.0"
3
+ version = "2.1.0"
4
4
  description = "Python library for interacting with multiple object storage APIs."
5
5
  authors = ["Indoc Systems"]
6
6
  readme = "README.md"