feat: support dynamic context for lance namespace#5710
feat: support dynamic context for lance namespace#5710jackye1995 merged 7 commits intolance-format:mainfrom
Conversation
PR Review: Dynamic Context Provider for Lance NamespaceSummaryThis PR adds a P0/P1 IssuesP1 - Performance: Rebuilding reqwest client on every request ( The fn config_with_context(&self, operation: &str, object_id: &str) -> Configuration {
// ...
let client = reqwest::Client::builder()
.default_headers(default_headers)
.build()
.unwrap_or_else(|_| reqwest::Client::new());
// ...
}Creating a new
P1 - Missing TLS/SSL configuration when rebuilding client ( When creating the new client in let client = reqwest::Client::builder()
.default_headers(default_headers)
.build() // No TLS configuration\!This means any REST namespace configured with client certificates or custom CA will silently lose those settings when a context provider is used. Other Observations (informational, not blocking)
|
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
This feature is similar to lancedb HeaderProvider, but implemented in a
more generic way.
In lance-namespace 0.4.5, we introduced per-request context which is a
free-form map that can be passed in, and processed by different
implementations differently. Based on that, we add a
`DynamicContextProvivder`.
Then specifically for `RestNamespace`, we define that any context
starting with `headers.` will be translated to a request level header.
Users can define an implementation of the `DynamicContextProvivder` in
python and java, and load it with for example:
Python:
```python
from lance.namespace import DynamicContextProvider
class MyAuthProvider(DynamicContextProvider):
def __init__(self, token: str, refresh_url: str):
self.token = token
self.refresh_url = refresh_url
def provide_context(self, info: dict) -> dict:
# info = {"operation": "list_tables", "object_id": "workspace"}
return {
"headers.Authorization": f"Bearer {self.token}",
"headers.X-Request-Id": str(uuid.uuid4()),
}
```
```python
from lance_namespace import connect, CreateNamespaceRequest, ListTablesRequest
ns = connect("rest", {
"uri": "http://localhost:8080",
"dynamic_context_provider.impl": "my_module.auth.MyAuthProvider",
"dynamic_context_provider.token": "secret-token",
"dynamic_context_provider.refresh_url": "https://auth.example.com/refresh",
})
# Use the namespace
ns.create_namespace(CreateNamespaceRequest(id=["workspace"]))
tables = ns.list_tables(ListTablesRequest(id=["workspace"]))
```
Java:
```java
package com.example.auth;
import org.lance.namespace.DynamicContextProvider;
import java.util.*;
public class MyAuthProvider implements DynamicContextProvider {
private final String token;
private final String refreshUrl;
// Constructor MUST accept Map<String, String>
public MyAuthProvider(Map<String, String> properties) {
this.token = properties.get("token");
this.refreshUrl = properties.get("refresh_url");
}
@OverRide
public Map<String, String> provideContext(String operation, String objectId) {
// operation = "list_tables", objectId = "workspace"
Map<String, String> context = new HashMap<>();
context.put("headers.Authorization", "Bearer " + token);
context.put("headers.X-Request-Id", UUID.randomUUID().toString());
return context;
}
}
```
```java
package com.example.app;
import org.lance.namespace.LanceNamespace;
import org.lance.namespace.model.*;
import org.apache.arrow.memory.RootAllocator;
import java.util.*;
Map<String, String> properties = new HashMap<>();
properties.put("uri", "http://localhost:8080");
properties.put("dynamic_context_provider.impl", "com.example.auth.MyAuthProvider");
properties.put("dynamic_context_provider.token", "secret-token");
properties.put("dynamic_context_provider.refresh_url", "https://auth.example.com/refresh");
try (BufferAllocator allocator = new RootAllocator();
LanceNamespace ns = LanceNamespace.connect("rest", properties, allocator)) {
// Use the namespace
ns.createNamespace(new CreateNamespaceRequest().id(Arrays.asList("workspace")));
ListTablesResponse tables = ns.listTables(new ListTablesRequest().id(Arrays.asList("workspace")));
}
```
Because of the requirement to dynamically inject per-request headers, we
also moved to use raw reqwest client now for all HTTP requests in
RestNamespace.
This feature is similar to lancedb HeaderProvider, but implemented in a
more generic way.
In lance-namespace 0.4.5, we introduced per-request context which is a
free-form map that can be passed in, and processed by different
implementations differently. Based on that, we add a
`DynamicContextProvivder`.
Then specifically for `RestNamespace`, we define that any context
starting with `headers.` will be translated to a request level header.
Users can define an implementation of the `DynamicContextProvivder` in
python and java, and load it with for example:
Python:
```python
from lance.namespace import DynamicContextProvider
class MyAuthProvider(DynamicContextProvider):
def __init__(self, token: str, refresh_url: str):
self.token = token
self.refresh_url = refresh_url
def provide_context(self, info: dict) -> dict:
# info = {"operation": "list_tables", "object_id": "workspace"}
return {
"headers.Authorization": f"Bearer {self.token}",
"headers.X-Request-Id": str(uuid.uuid4()),
}
```
```python
from lance_namespace import connect, CreateNamespaceRequest, ListTablesRequest
ns = connect("rest", {
"uri": "http://localhost:8080",
"dynamic_context_provider.impl": "my_module.auth.MyAuthProvider",
"dynamic_context_provider.token": "secret-token",
"dynamic_context_provider.refresh_url": "https://auth.example.com/refresh",
})
# Use the namespace
ns.create_namespace(CreateNamespaceRequest(id=["workspace"]))
tables = ns.list_tables(ListTablesRequest(id=["workspace"]))
```
Java:
```java
package com.example.auth;
import org.lance.namespace.DynamicContextProvider;
import java.util.*;
public class MyAuthProvider implements DynamicContextProvider {
private final String token;
private final String refreshUrl;
// Constructor MUST accept Map<String, String>
public MyAuthProvider(Map<String, String> properties) {
this.token = properties.get("token");
this.refreshUrl = properties.get("refresh_url");
}
@OverRide
public Map<String, String> provideContext(String operation, String objectId) {
// operation = "list_tables", objectId = "workspace"
Map<String, String> context = new HashMap<>();
context.put("headers.Authorization", "Bearer " + token);
context.put("headers.X-Request-Id", UUID.randomUUID().toString());
return context;
}
}
```
```java
package com.example.app;
import org.lance.namespace.LanceNamespace;
import org.lance.namespace.model.*;
import org.apache.arrow.memory.RootAllocator;
import java.util.*;
Map<String, String> properties = new HashMap<>();
properties.put("uri", "http://localhost:8080");
properties.put("dynamic_context_provider.impl", "com.example.auth.MyAuthProvider");
properties.put("dynamic_context_provider.token", "secret-token");
properties.put("dynamic_context_provider.refresh_url", "https://auth.example.com/refresh");
try (BufferAllocator allocator = new RootAllocator();
LanceNamespace ns = LanceNamespace.connect("rest", properties, allocator)) {
// Use the namespace
ns.createNamespace(new CreateNamespaceRequest().id(Arrays.asList("workspace")));
ListTablesResponse tables = ns.listTables(new ListTablesRequest().id(Arrays.asList("workspace")));
}
```
Because of the requirement to dynamically inject per-request headers, we
also moved to use raw reqwest client now for all HTTP requests in
RestNamespace.
This feature is similar to lancedb HeaderProvider, but implemented in a more generic way. In lance-namespace 0.4.5, we introduced per-request context which is a free-form map that can be passed in, and processed by different implementations differently. Based on that, we add a `DynamicContextProvider`. Then specifically for `RestNamespace`, we define that any context starting with `headers.` will be translated to a request level header. Because of the requirement to dynamically inject per-request headers, we also moved to use raw reqwest client now for all HTTP requests in RestNamespace. Adapted for release branch by using Python::with_gil instead of Python::attach (pyo3 0.25 vs 0.26). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This feature is similar to lancedb HeaderProvider, but implemented in a more generic way. In lance-namespace 0.4.5, we introduced per-request context which is a free-form map that can be passed in, and processed by different implementations differently. Based on that, we add a `DynamicContextProvider`. Then specifically for `RestNamespace`, we define that any context starting with `headers.` will be translated to a request level header. Because of the requirement to dynamically inject per-request headers, we also moved to use raw reqwest client now for all HTTP requests in RestNamespace. Adapted for release branch by using Python::with_gil instead of Python::attach (pyo3 0.25 vs 0.26). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This feature is similar to lancedb HeaderProvider, but implemented in a more generic way.
In lance-namespace 0.4.5, we introduced per-request context which is a free-form map that can be passed in, and processed by different implementations differently. Based on that, we add a
DynamicContextProvivder.Then specifically for
RestNamespace, we define that any context starting withheaders.will be translated to a request level header.Users can define an implementation of the
DynamicContextProvivderin python and java, and load it with for example:Python:
Java:
Because of the requirement to dynamically inject per-request headers, we also moved to use raw reqwest client now for all HTTP requests in RestNamespace.