objectstore-client 0.2.2__tar.gz → 0.2.4__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.2
3
+ Version: 0.2.4
4
4
  Summary: Client SDK for Objectstore, the Sentry object storage platform
5
5
  Author: Sentry
6
6
  Author-email: Sentry <oss@sentry.io>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "objectstore-client"
3
- version = "0.2.2"
3
+ version = "0.2.4"
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" }]
@@ -5,6 +5,7 @@ import warnings
5
5
  from collections.abc import Mapping, Sequence
6
6
  from dataclasses import asdict, dataclass
7
7
  from datetime import UTC, datetime, timedelta
8
+ from importlib.metadata import version
8
9
  from io import BytesIO
9
10
  from typing import IO, Any, Literal, NamedTuple, cast
10
11
  from urllib.parse import urlparse
@@ -38,6 +39,9 @@ from objectstore_client.scope import Scope
38
39
  # Query parameter carrying a JWT, mirroring the `x-os-auth` header.
39
40
  PARAM_AUTH = "os_auth"
40
41
 
42
+ # Identifies this library to the server, mirroring the Rust client's user agent.
43
+ USER_AGENT = f"objectstore-client/{version('objectstore-client')}"
44
+
41
45
 
42
46
  class GetResponse(NamedTuple):
43
47
  metadata: Metadata
@@ -100,8 +104,13 @@ class Client:
100
104
  "http://objectstore:8888"). metrics_backend: Optional metrics backend for
101
105
  tracking storage operations. Defaults to ``NoOpMetricsBackend`` if not
102
106
  provided.
103
- propagate_traces: Whether to propagate Sentry trace headers in requests to
104
- objectstore. Defaults to ``False``.
107
+ propagate_traces: **Deprecated.** Use Sentry's ``StdlibIntegration``
108
+ instead; it propagates traces on the underlying HTTP request
109
+ regardless of this flag, unless the host application's Sentry SDK
110
+ is configured to opt out.
111
+
112
+ This only controls headers this client adds itself. Defaults to
113
+ ``False``.
105
114
  retries: Number of connection retries for failed requests.
106
115
  Defaults to ``3`` if not specified. **Note:** only connection failures are
107
116
  retried, not read failures (as compression streams cannot be rewound).
@@ -122,7 +131,9 @@ class Client:
122
131
 
123
132
  connection_kwargs: Additional keyword arguments to pass to the underlying
124
133
  urllib3 connection pool (e.g., custom headers, SSL settings, advanced
125
- timeouts).
134
+ timeouts). By default, requests carry a ``User-Agent`` header of
135
+ ``objectstore-client/{version}``; pass a ``headers`` mapping with a
136
+ ``User-Agent`` key here to override it.
126
137
  token: A ``SecretKey`` that signs a fresh JWT for each request
127
138
  using an EdDSA keypair, or a static pre-signed JWT string used
128
139
  as-is for every request. Use a ``SecretKey`` for internal
@@ -140,6 +151,14 @@ class Client:
140
151
  connection_kwargs: Mapping[str, Any] | None = None,
141
152
  token: TokenProvider | None = None,
142
153
  ):
154
+ if propagate_traces:
155
+ warnings.warn(
156
+ "`propagate_traces` is deprecated; Sentry's `StdlibIntegration` "
157
+ "already propagates traces on the underlying HTTP request",
158
+ DeprecationWarning,
159
+ stacklevel=2,
160
+ )
161
+
143
162
  connection_kwargs_to_use = asdict(_ConnectionDefaults())
144
163
 
145
164
  if retries:
@@ -158,6 +177,10 @@ class Client:
158
177
  if connection_kwargs:
159
178
  connection_kwargs_to_use = {**connection_kwargs_to_use, **connection_kwargs}
160
179
 
180
+ headers = urllib3.HTTPHeaderDict({"User-Agent": USER_AGENT})
181
+ headers.update(connection_kwargs_to_use.get("headers") or {})
182
+ connection_kwargs_to_use["headers"] = headers
183
+
161
184
  self._pool = urllib3.connectionpool.connection_from_url(
162
185
  base_url, **connection_kwargs_to_use
163
186
  )
@@ -148,37 +148,47 @@ def parse_expiration(value: str) -> ExpirationPolicy | None:
148
148
 
149
149
 
150
150
  def format_timedelta(delta: timedelta) -> str:
151
- days = delta.days
152
- output = f"{days} days" if days else ""
153
- if seconds := delta.seconds:
154
- if output:
155
- output += " "
156
- output += f"{seconds} seconds"
151
+ """
152
+ Formats a duration in the wire format, such as `400d 1m 30s`.
153
+
154
+ Days are the largest unit, components that are zero are omitted, and a zero duration
155
+ is written as `0s`. Any sub-second remainder is truncated.
156
+ """
157
+ minutes, seconds = divmod(delta.seconds, 60)
158
+ hours, minutes = divmod(minutes, 60)
159
+
160
+ components = ((delta.days, "d"), (hours, "h"), (minutes, "m"), (seconds, "s"))
161
+ output = " ".join(f"{value}{unit}" for value, unit in components if value)
157
162
 
158
- return output
163
+ return output or "0s"
159
164
 
160
165
 
161
166
  TIME_SPLIT = re.compile(r"[^\W\d_]+|\d+")
162
167
 
163
168
 
164
169
  def parse_timedelta(delta: str) -> timedelta:
170
+ """
171
+ Parses a duration in the wire format, such as `400d 1m 30s`.
172
+
173
+ Units are matched exactly, so anything outside the wire format raises a
174
+ `ValueError` rather than being mistaken for a unit it merely starts with.
175
+ """
165
176
  words = TIME_SPLIT.findall(delta)
166
177
  seconds = 0
167
178
 
168
179
  for num, unit in itertools_batched(words, n=2, strict=True):
169
180
  num = int(num)
170
- multiplier = 0
171
181
 
172
- if unit.startswith("w"):
173
- multiplier = 86400 * 7
174
- elif unit.startswith("d"):
182
+ if unit == "d":
175
183
  multiplier = 86400
176
- elif unit.startswith("h"):
184
+ elif unit == "h":
177
185
  multiplier = 3600
178
- elif unit.startswith("m") and not unit.startswith("ms"):
186
+ elif unit == "m":
179
187
  multiplier = 60
180
- elif unit.startswith("s"):
188
+ elif unit == "s":
181
189
  multiplier = 1
190
+ else:
191
+ raise ValueError(f"unknown time unit {unit!r} in duration {delta!r}")
182
192
 
183
193
  seconds += num * multiplier
184
194