Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions clients/python/src/objectstore_client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from dataclasses import asdict, dataclass
from io import BytesIO
from typing import IO, Any, Literal, NamedTuple, cast
from urllib.parse import urlparse

import sentry_sdk
import urllib3
Expand Down Expand Up @@ -121,6 +122,7 @@ def __init__(
self._pool = urllib3.connectionpool.connection_from_url(
base_url, **connection_kwargs_to_use
)
self._base_path = urlparse(base_url).path
self._metrics_backend = metrics_backend or NoOpMetricsBackend()
self._propagate_traces = propagate_traces

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

return Session(
self._pool,
self._base_path,
self._metrics_backend,
self._propagate_traces,
usecase,
Expand All @@ -189,28 +192,33 @@ class Session:
def __init__(
self,
pool: HTTPConnectionPool,
base_path: str,
metrics_backend: MetricsBackend,
propagate_traces: bool,
usecase: Usecase,
scope: str,
):
self._pool = pool
self._base_path = base_path
self._metrics_backend = metrics_backend
self._propagate_traces = propagate_traces
self._usecase = usecase
self._scope = scope

def _make_headers(self) -> dict[str, str]:
headers = dict(self._pool.headers)
if self._propagate_traces:
return dict(sentry_sdk.get_current_scope().iter_trace_propagation_headers())
return {}
headers.update(
dict(sentry_sdk.get_current_scope().iter_trace_propagation_headers())
)
return headers

def _make_url(self, key: str | None, full: bool = False) -> str:
base_path = f"/v1/{self._usecase.name}/{self._scope}/objects/{key or ''}"
relative_path = f"/v1/{self._usecase.name}/{self._scope}/objects/{key or ''}"
path = self._base_path.rstrip("/") + relative_path
if full:
return f"http://{self._pool.host}:{self._pool.port}{base_path}"
else:
return f"{base_path}"
return f"http://{self._pool.host}:{self._pool.port}{path}"
return path

def put(
self,
Expand Down
10 changes: 10 additions & 0 deletions clients/python/tests/test_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,13 @@ def test_object_url() -> None:
session.object_url("foo/bar")
== "http://127.0.0.1:8888/v1/testing/org.12345/project.1337/app_slug.email_app/objects/foo/bar"
)


def test_object_url_with_base_path() -> None:
client = Client("http://127.0.0.1:8888/api/prefix")
session = client.session(Usecase("testing"), org=12345, project=1337)

assert (
session.object_url("foo/bar")
== "http://127.0.0.1:8888/api/prefix/v1/testing/org.12345/project.1337/objects/foo/bar"
)
39 changes: 33 additions & 6 deletions clients/rust/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ impl ClientBuilder {
Ok(url) => url,
Err(err) => return Self(Err(err.into())),
};
if service_url.cannot_be_a_base() {
return ClientBuilder(Err(crate::Error::InvalidUrl {
message: "service_url cannot be a base".to_owned(),
}));
}

let reqwest_builder = reqwest::Client::builder()
// The read timeout "applies to each read operation", so should work fine for larger
Expand Down Expand Up @@ -107,11 +112,12 @@ impl ClientBuilder {
/// # Errors
///
/// This method fails if:
/// - the given `service_url` is invalid
/// - the given `service_url` is invalid or cannot be used as a base URL
/// - the [`reqwest::Client`] fails to build. Refer to [`reqwest::ClientBuilder::build`] for
/// more information on when this can happen.
pub fn build(self) -> crate::Result<Client> {
let inner = self.0?.apply_defaults();

Ok(Client {
inner: Arc::new(ClientInner {
reqwest: inner.reqwest_builder.build()?,
Expand Down Expand Up @@ -417,11 +423,19 @@ impl Session {
/// in particular in relation to `Accept-Encoding`.
pub fn object_url(&self, object_key: &str) -> Url {
let mut url = self.client.service_url.clone();
let path = format!(
"v1/{}/{}/objects/{object_key}",
self.scope.usecase.name, self.scope.scope
);
url.set_path(&path);

// `path_segments_mut` can only error if the url is cannot-be-a-base,
// and we check that in `ClientBuilder::new`, therefore this will never panic.
let mut segments = url.path_segments_mut().unwrap();
segments
.push("v1")
.extend(self.scope.usecase.name.split("/"));
if !self.scope.scope.is_empty() {
segments.extend(self.scope.scope.split("/"));
}
segments.push("objects").extend(object_key.split("/"));
drop(segments);

url
}

Expand Down Expand Up @@ -464,4 +478,17 @@ mod tests {
"http://127.0.0.1:8888/v1/testing/org.12345/project.1337/app_slug.email_app/objects/foo/bar"
)
}

#[test]
fn test_object_url_with_base_path() {
let client = Client::new("http://127.0.0.1:8888/api/prefix").unwrap();
let usecase = Usecase::new("testing");
let scope = usecase.for_project(12345, 1337);
let session = client.session(scope).unwrap();

assert_eq!(
session.object_url("foo/bar").to_string(),
"http://127.0.0.1:8888/api/prefix/v1/testing/org.12345/project.1337/objects/foo/bar"
)
}
}