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.
- {objectstore_client-0.2.1 → objectstore_client-0.2.2}/PKG-INFO +10 -6
- {objectstore_client-0.2.1 → objectstore_client-0.2.2}/README.md +9 -5
- {objectstore_client-0.2.1 → objectstore_client-0.2.2}/pyproject.toml +1 -1
- {objectstore_client-0.2.1 → objectstore_client-0.2.2}/src/objectstore_client/client.py +38 -12
- {objectstore_client-0.2.1 → objectstore_client-0.2.2}/LICENSE.md +0 -0
- {objectstore_client-0.2.1 → objectstore_client-0.2.2}/src/objectstore_client/__init__.py +0 -0
- {objectstore_client-0.2.1 → objectstore_client-0.2.2}/src/objectstore_client/auth.py +0 -0
- {objectstore_client-0.2.1 → objectstore_client-0.2.2}/src/objectstore_client/errors.py +0 -0
- {objectstore_client-0.2.1 → objectstore_client-0.2.2}/src/objectstore_client/metadata.py +0 -0
- {objectstore_client-0.2.1 → objectstore_client-0.2.2}/src/objectstore_client/metrics.py +0 -0
- {objectstore_client-0.2.1 → objectstore_client-0.2.2}/src/objectstore_client/multipart.py +0 -0
- {objectstore_client-0.2.1 → objectstore_client-0.2.2}/src/objectstore_client/presign.py +0 -0
- {objectstore_client-0.2.1 → objectstore_client-0.2.2}/src/objectstore_client/py.typed +0 -0
- {objectstore_client-0.2.1 → objectstore_client-0.2.2}/src/objectstore_client/scope.py +0 -0
- {objectstore_client-0.2.1 → objectstore_client-0.2.2}/src/objectstore_client/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: objectstore-client
|
|
3
|
-
Version: 0.2.
|
|
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
|
|
199
|
-
|
|
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
|
-
|
|
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
|
-
|
|
220
|
-
|
|
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
|
|
80
|
-
|
|
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
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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,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
|
|
336
|
-
|
|
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
|
-
|
|
343
|
-
if
|
|
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
|
|
374
|
-
# For
|
|
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
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|