objectstore-client 0.2.1__tar.gz → 0.2.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: objectstore-client
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Client SDK for Objectstore, the Sentry object storage platform
5
5
  Author: Sentry
6
6
  Author-email: Sentry <oss@sentry.io>
@@ -195,11 +195,15 @@ session.put(b"payload", origin="203.0.113.42")
195
195
 
196
196
  ### Compression
197
197
 
198
- Uploads are compressed with Zstd by default. Downloads are transparently decompressed.
199
- You can override compression per-upload for pre-compressed or uncompressible data.
198
+ Uploads are compressed with Zstd by default, and downloads are transparently decompressed.
199
+ Compression can be overridden per-upload:
200
200
 
201
201
  ```python
202
- session.put(already_compressed_data, compression="none")
202
+ # upload as-is and record no encoding:
203
+ session.put(video_data, compress="none")
204
+
205
+ # upload as-is, but record the encoding so that downloads still decompress:
206
+ session.put(zstd_data, precompressed="zstd")
203
207
  ```
204
208
 
205
209
  ### Custom Metadata
@@ -216,8 +220,8 @@ For large objects, use multipart uploads to upload parts independently and then
216
220
  assemble them into a final object.
217
221
 
218
222
  **Important:** unlike single-object uploads, multipart uploads do **not** auto-compress.
219
- The caller must pre-compress each part according to the compression set as part of the metadata
220
- when initiating the upload.
223
+ `compression` on `initiate_multipart_upload` behaves like `precompressed` above: it only
224
+ records the algorithm, and the caller must pre-compress each part accordingly.
221
225
 
222
226
  ```python
223
227
  from concurrent.futures import ThreadPoolExecutor
@@ -76,11 +76,15 @@ session.put(b"payload", origin="203.0.113.42")
76
76
 
77
77
  ### Compression
78
78
 
79
- Uploads are compressed with Zstd by default. Downloads are transparently decompressed.
80
- You can override compression per-upload for pre-compressed or uncompressible data.
79
+ Uploads are compressed with Zstd by default, and downloads are transparently decompressed.
80
+ Compression can be overridden per-upload:
81
81
 
82
82
  ```python
83
- session.put(already_compressed_data, compression="none")
83
+ # upload as-is and record no encoding:
84
+ session.put(video_data, compress="none")
85
+
86
+ # upload as-is, but record the encoding so that downloads still decompress:
87
+ session.put(zstd_data, precompressed="zstd")
84
88
  ```
85
89
 
86
90
  ### Custom Metadata
@@ -97,8 +101,8 @@ For large objects, use multipart uploads to upload parts independently and then
97
101
  assemble them into a final object.
98
102
 
99
103
  **Important:** unlike single-object uploads, multipart uploads do **not** auto-compress.
100
- The caller must pre-compress each part according to the compression set as part of the metadata
101
- when initiating the upload.
104
+ `compression` on `initiate_multipart_upload` behaves like `precompressed` above: it only
105
+ records the algorithm, and the caller must pre-compress each part accordingly.
102
106
 
103
107
  ```python
104
108
  from concurrent.futures import ThreadPoolExecutor
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "objectstore-client"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  description = "Client SDK for Objectstore, the Sentry object storage platform"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Sentry", email = "oss@sentry.io" }]
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import math
4
+ import warnings
4
5
  from collections.abc import Mapping, Sequence
5
6
  from dataclasses import asdict, dataclass
6
7
  from datetime import UTC, datetime, timedelta
@@ -310,7 +311,9 @@ class Session:
310
311
  self,
311
312
  contents: bytes | IO[bytes],
312
313
  key: str | None = None,
314
+ compress: Compression | None = None,
313
315
  compression: Compression | Literal["none"] | None = None,
316
+ precompressed: Compression | None = None,
314
317
  content_type: str | None = None,
315
318
  metadata: dict[str, str] | None = None,
316
319
  expiration_policy: ExpirationPolicy | None = None,
@@ -324,27 +327,49 @@ class Session:
324
327
  from this function.
325
328
 
326
329
  The client will select the configured `default_compression` if none is given
327
- explicitly.
328
- This can be overridden by explicitly giving a `compression` argument.
330
+ explicitly. This can be overridden by explicitly giving a `compress` argument.
329
331
  Providing `"none"` as the argument will instruct the client to not apply
330
332
  any compression to this upload, which is useful for uncompressible formats.
331
333
 
334
+ If `contents` is already compressed, pass the algorithm as `precompressed`
335
+ instead. The payload is then uploaded verbatim and the algorithm is recorded
336
+ in the object's metadata, so downloads decompress it transparently.
337
+
332
338
  You can use the utility function `objectstore_client.utils.guess_mime_type`
333
339
  to attempt to guess a `content_type` based on magic bytes.
340
+
341
+ `compression` is deprecated in favor of `compress`.
334
342
  """
335
- if compression and compression not in ("none", "zstd"):
336
- raise ValueError(f"Invalid compression: {compression}")
343
+ if compression is not None:
344
+ warnings.warn(
345
+ "`compression` is deprecated, use `compress` instead",
346
+ DeprecationWarning,
347
+ stacklevel=2,
348
+ )
349
+ if compress is not None:
350
+ raise ValueError("Cannot pass both `compress` and `compression`")
351
+ compress = compression
352
+
353
+ if compress is not None and precompressed is not None:
354
+ raise ValueError("Cannot pass both `compress` and `precompressed`")
355
+ if compress and compress not in ("none", "zstd"):
356
+ raise ValueError(f"Invalid compression: {compress}")
357
+ if precompressed and precompressed != "zstd":
358
+ raise ValueError(f"Invalid compression: {precompressed}")
337
359
 
338
360
  headers = self._make_headers()
339
361
  body = BytesIO(contents) if isinstance(contents, bytes) else contents
340
362
  original_body: IO[bytes] = body
341
363
 
342
- compression = compression or self._usecase._compression
343
- if compression == "zstd":
364
+ encoding = precompressed or compress or self._usecase._compression
365
+ if encoding != "none":
366
+ headers["Content-Encoding"] = encoding
367
+
368
+ compress_with = encoding if precompressed is None else "none"
369
+ if compress_with == "zstd":
344
370
  cctx = zstandard.ZstdCompressor()
345
371
  body = cctx.stream_reader(original_body)
346
372
  body = cast(IO[bytes], utils._ZstdCompressionReaderWrapper(body))
347
- headers["Content-Encoding"] = "zstd"
348
373
 
349
374
  if content_type:
350
375
  headers["Content-Type"] = content_type
@@ -370,8 +395,8 @@ class Session:
370
395
  self._metrics_backend, "put", self._usecase.name
371
396
  ) as metric_emitter:
372
397
  retries = None # by default use the pool's value, set by the Client
373
- if compression == "zstd":
374
- # For compressed bodies, don't attempt read retries,
398
+ if compress_with != "none":
399
+ # For on-the-fly compression, don't attempt read retries,
375
400
  # as the stream cannot be rewound after data has been consumed.
376
401
  pool_retries = self._pool.retries
377
402
  if isinstance(pool_retries, urllib3.Retry):
@@ -393,9 +418,10 @@ class Session:
393
418
 
394
419
  # Must do this after streaming `body` as that's what is responsible
395
420
  # for advancing the seek position in both streams
396
- metric_emitter.record_uncompressed_size(original_body.tell())
397
- if compression and compression != "none":
398
- metric_emitter.record_compressed_size(body.tell(), compression)
421
+ if precompressed is None:
422
+ metric_emitter.record_uncompressed_size(original_body.tell())
423
+ if encoding != "none":
424
+ metric_emitter.record_compressed_size(body.tell(), encoding)
399
425
  return res["key"]
400
426
 
401
427
  def get(