Skip to content

Commit e840bb8

Browse files
authored
feat(clients): Append to the given base URL path (#222)
1 parent eb2bbaf commit e840bb8

File tree

3 files changed

+57
-12
lines changed

3 files changed

+57
-12
lines changed

clients/python/src/objectstore_client/client.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from dataclasses import asdict, dataclass
66
from io import BytesIO
77
from typing import IO, Any, Literal, NamedTuple, cast
8+
from urllib.parse import urlparse
89

910
import sentry_sdk
1011
import urllib3
@@ -121,6 +122,7 @@ def __init__(
121122
self._pool = urllib3.connectionpool.connection_from_url(
122123
base_url, **connection_kwargs_to_use
123124
)
125+
self._base_path = urlparse(base_url).path
124126
self._metrics_backend = metrics_backend or NoOpMetricsBackend()
125127
self._propagate_traces = propagate_traces
126128

@@ -172,6 +174,7 @@ def session(self, usecase: Usecase, **scopes: str | int | bool) -> Session:
172174

173175
return Session(
174176
self._pool,
177+
self._base_path,
175178
self._metrics_backend,
176179
self._propagate_traces,
177180
usecase,
@@ -189,28 +192,33 @@ class Session:
189192
def __init__(
190193
self,
191194
pool: HTTPConnectionPool,
195+
base_path: str,
192196
metrics_backend: MetricsBackend,
193197
propagate_traces: bool,
194198
usecase: Usecase,
195199
scope: str,
196200
):
197201
self._pool = pool
202+
self._base_path = base_path
198203
self._metrics_backend = metrics_backend
199204
self._propagate_traces = propagate_traces
200205
self._usecase = usecase
201206
self._scope = scope
202207

203208
def _make_headers(self) -> dict[str, str]:
209+
headers = dict(self._pool.headers)
204210
if self._propagate_traces:
205-
return dict(sentry_sdk.get_current_scope().iter_trace_propagation_headers())
206-
return {}
211+
headers.update(
212+
dict(sentry_sdk.get_current_scope().iter_trace_propagation_headers())
213+
)
214+
return headers
207215

208216
def _make_url(self, key: str | None, full: bool = False) -> str:
209-
base_path = f"/v1/{self._usecase.name}/{self._scope}/objects/{key or ''}"
217+
relative_path = f"/v1/{self._usecase.name}/{self._scope}/objects/{key or ''}"
218+
path = self._base_path.rstrip("/") + relative_path
210219
if full:
211-
return f"http://{self._pool.host}:{self._pool.port}{base_path}"
212-
else:
213-
return f"{base_path}"
220+
return f"http://{self._pool.host}:{self._pool.port}{path}"
221+
return path
214222

215223
def put(
216224
self,

clients/python/tests/test_smoke.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,13 @@ def test_object_url() -> None:
1616
session.object_url("foo/bar")
1717
== "http://127.0.0.1:8888/v1/testing/org.12345/project.1337/app_slug.email_app/objects/foo/bar"
1818
)
19+
20+
21+
def test_object_url_with_base_path() -> None:
22+
client = Client("http://127.0.0.1:8888/api/prefix")
23+
session = client.session(Usecase("testing"), org=12345, project=1337)
24+
25+
assert (
26+
session.object_url("foo/bar")
27+
== "http://127.0.0.1:8888/api/prefix/v1/testing/org.12345/project.1337/objects/foo/bar"
28+
)

clients/rust/src/client.rs

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ impl ClientBuilder {
4848
Ok(url) => url,
4949
Err(err) => return Self(Err(err.into())),
5050
};
51+
if service_url.cannot_be_a_base() {
52+
return ClientBuilder(Err(crate::Error::InvalidUrl {
53+
message: "service_url cannot be a base".to_owned(),
54+
}));
55+
}
5156

5257
let reqwest_builder = reqwest::Client::builder()
5358
// The read timeout "applies to each read operation", so should work fine for larger
@@ -107,11 +112,12 @@ impl ClientBuilder {
107112
/// # Errors
108113
///
109114
/// This method fails if:
110-
/// - the given `service_url` is invalid
115+
/// - the given `service_url` is invalid or cannot be used as a base URL
111116
/// - the [`reqwest::Client`] fails to build. Refer to [`reqwest::ClientBuilder::build`] for
112117
/// more information on when this can happen.
113118
pub fn build(self) -> crate::Result<Client> {
114119
let inner = self.0?.apply_defaults();
120+
115121
Ok(Client {
116122
inner: Arc::new(ClientInner {
117123
reqwest: inner.reqwest_builder.build()?,
@@ -417,11 +423,19 @@ impl Session {
417423
/// in particular in relation to `Accept-Encoding`.
418424
pub fn object_url(&self, object_key: &str) -> Url {
419425
let mut url = self.client.service_url.clone();
420-
let path = format!(
421-
"v1/{}/{}/objects/{object_key}",
422-
self.scope.usecase.name, self.scope.scope
423-
);
424-
url.set_path(&path);
426+
427+
// `path_segments_mut` can only error if the url is cannot-be-a-base,
428+
// and we check that in `ClientBuilder::new`, therefore this will never panic.
429+
let mut segments = url.path_segments_mut().unwrap();
430+
segments
431+
.push("v1")
432+
.extend(self.scope.usecase.name.split("/"));
433+
if !self.scope.scope.is_empty() {
434+
segments.extend(self.scope.scope.split("/"));
435+
}
436+
segments.push("objects").extend(object_key.split("/"));
437+
drop(segments);
438+
425439
url
426440
}
427441

@@ -464,4 +478,17 @@ mod tests {
464478
"http://127.0.0.1:8888/v1/testing/org.12345/project.1337/app_slug.email_app/objects/foo/bar"
465479
)
466480
}
481+
482+
#[test]
483+
fn test_object_url_with_base_path() {
484+
let client = Client::new("http://127.0.0.1:8888/api/prefix").unwrap();
485+
let usecase = Usecase::new("testing");
486+
let scope = usecase.for_project(12345, 1337);
487+
let session = client.session(scope).unwrap();
488+
489+
assert_eq!(
490+
session.object_url("foo/bar").to_string(),
491+
"http://127.0.0.1:8888/api/prefix/v1/testing/org.12345/project.1337/objects/foo/bar"
492+
)
493+
}
467494
}

0 commit comments

Comments
 (0)