Skip to content

Commit 6efbd21

Browse files
Merge pull request #122 from gleanwork/cfreeman/server-url-normalization
Add server URL normalization for flexible server_url input
2 parents 128c3ef + cd54824 commit 6efbd21

File tree

4 files changed

+123
-8
lines changed

4 files changed

+123
-8
lines changed

README.md

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ Each namespace has its own authentication requirements and access patterns. Whil
1717
from glean.api_client import Glean
1818
import os
1919

20-
with Glean(api_token="client-token", instance="instance-name") as glean:
20+
with Glean(api_token="client-token", server_url="https://mycompany-be.glean.com") as glean:
2121
search_response = glean.client.search.query(query="search term")
2222

2323
print(search_response)
@@ -26,7 +26,7 @@ with Glean(api_token="client-token", instance="instance-name") as glean:
2626
from glean.api_client import Glean, models
2727
import os
2828

29-
with Glean(api_token="indexing-token", instance="instance-name") as glean:
29+
with Glean(api_token="indexing-token", server_url="https://mycompany-be.glean.com") as glean:
3030
document_response = glean.indexing.documents.index(
3131
document=models.Document(
3232
id="doc-123",
@@ -744,14 +744,34 @@ By default, an API error will raise a errors.GleanError exception, which has the
744744
<!-- Start Server Selection [server] -->
745745
## Server Selection
746746

747-
### Server Variables
747+
### Server URL
748748

749-
The default server `https://{instance}-be.glean.com` contains variables and is set to `https://instance-name-be.glean.com` by default. To override default values, the following parameters are available when initializing the SDK client instance:
749+
The recommended way to configure the Glean API server is to pass `server_url` when initializing the SDK client:
750+
751+
```python
752+
from glean.api_client import Glean
753+
754+
glean = Glean(
755+
api_token="your-api-token",
756+
server_url="https://mycompany-be.glean.com",
757+
)
758+
```
759+
760+
Schemeless URLs (e.g., `"mycompany-be.glean.com"`) are also accepted — the SDK will automatically prepend `https://`.
761+
762+
You can also configure the server URL via the `GLEAN_SERVER_URL` environment variable.
763+
764+
### Server Variables (backwards compatible)
765+
766+
For backwards compatibility, the SDK also supports the `instance` parameter, which constructs the server URL using the pattern `https://{instance}-be.glean.com`:
750767

751768
| Variable | Parameter | Default | Description |
752769
| ---------- | --------------- | ----------------- | ------------------------------------------------------------------------------------------------------ |
753770
| `instance` | `instance: str` | `"instance-name"` | The instance name (typically the email domain without the TLD) that determines the deployment backend. |
754771

772+
> [!NOTE]
773+
> The `server_url` parameter is preferred over `instance`. If both are provided, `server_url` takes precedence.
774+
755775
#### Example
756776

757777
```python
@@ -761,8 +781,7 @@ import os
761781

762782

763783
with Glean(
764-
server_idx=0,
765-
instance="instance-name",
784+
server_url="https://mycompany-be.glean.com",
766785
api_token=os.getenv("GLEAN_API_TOKEN", ""),
767786
) as glean:
768787

@@ -989,7 +1008,7 @@ from glean.api_client import Glean
9891008

9901009
glean = Glean(
9911010
api_token=os.environ.get("GLEAN_API_TOKEN", ""),
992-
instance=os.environ.get("GLEAN_INSTANCE", ""),
1011+
server_url="https://mycompany-be.glean.com",
9931012
)
9941013
```
9951014

@@ -1002,7 +1021,7 @@ from glean.api_client import Glean
10021021

10031022
glean = Glean(
10041023
api_token=os.environ.get("GLEAN_API_TOKEN", ""),
1005-
instance=os.environ.get("GLEAN_INSTANCE", ""),
1024+
server_url="https://mycompany-be.glean.com",
10061025
exclude_deprecated_after="2026-10-15",
10071026
include_experimental=True,
10081027
)

src/glean/api_client/_hooks/registration.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .types import Hooks
2+
from .server_url_normalizer import ServerURLNormalizerHook
23
from .multipart_fix_hook import MultipartFileFieldFixHook
34
from .agent_file_upload_error_hook import AgentFileUploadErrorHook
45
from .x_glean import XGlean
@@ -15,6 +16,9 @@ def init_hooks(hooks: Hooks):
1516
with an instance of a hook that implements that specific Hook interface
1617
Hooks are registered per SDK instance, and are valid for the lifetime of the SDK instance"""
1718

19+
# Register hook to normalize server URLs (prepend https:// if no scheme provided)
20+
hooks.register_sdk_init_hook(ServerURLNormalizerHook())
21+
1822
# Register hook to fix multipart file field names that incorrectly have '[]' suffix
1923
hooks.register_sdk_init_hook(MultipartFileFieldFixHook())
2024

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""Hook to normalize server URLs, prepending https:// if no scheme is provided."""
2+
3+
import re
4+
from typing import Tuple
5+
from .types import SDKInitHook
6+
from glean.api_client.httpclient import HttpClient
7+
8+
9+
def normalize_server_url(url: str) -> str:
10+
normalized = url
11+
if not re.match(r'^https?://', normalized, re.IGNORECASE):
12+
normalized = f'https://{normalized}'
13+
normalized = normalized.rstrip('/')
14+
return normalized
15+
16+
17+
class ServerURLNormalizerHook(SDKInitHook):
18+
"""Normalizes server URLs by prepending https:// if no scheme is provided."""
19+
20+
def sdk_init(self, base_url: str, client: HttpClient) -> Tuple[str, HttpClient]:
21+
return normalize_server_url(base_url), client
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""Tests for the server URL normalizer hook."""
2+
3+
from unittest.mock import Mock
4+
5+
import pytest
6+
7+
from src.glean.api_client._hooks.server_url_normalizer import (
8+
ServerURLNormalizerHook,
9+
normalize_server_url,
10+
)
11+
from src.glean.api_client.httpclient import HttpClient
12+
13+
14+
class TestNormalizeServerUrl:
15+
"""Test cases for the normalize_server_url function."""
16+
17+
def test_no_scheme_prepends_https(self):
18+
assert normalize_server_url("example.glean.com") == "https://example.glean.com"
19+
20+
def test_https_preserved(self):
21+
assert normalize_server_url("https://example.glean.com") == "https://example.glean.com"
22+
23+
def test_http_localhost_preserved(self):
24+
assert normalize_server_url("http://localhost:8080") == "http://localhost:8080"
25+
26+
def test_http_non_localhost_preserved(self):
27+
assert normalize_server_url("http://example.glean.com") == "http://example.glean.com"
28+
29+
def test_trailing_slash_stripped(self):
30+
assert normalize_server_url("https://example.glean.com/") == "https://example.glean.com"
31+
32+
def test_multiple_trailing_slashes_stripped(self):
33+
assert normalize_server_url("https://example.glean.com///") == "https://example.glean.com"
34+
35+
def test_no_scheme_with_trailing_slash(self):
36+
assert normalize_server_url("example.glean.com/") == "https://example.glean.com"
37+
38+
def test_url_with_path(self):
39+
assert normalize_server_url("https://example.glean.com/api/v1") == "https://example.glean.com/api/v1"
40+
41+
def test_url_with_path_and_trailing_slash(self):
42+
assert normalize_server_url("https://example.glean.com/api/v1/") == "https://example.glean.com/api/v1"
43+
44+
def test_no_scheme_with_path(self):
45+
assert normalize_server_url("example.glean.com/api/v1") == "https://example.glean.com/api/v1"
46+
47+
def test_case_insensitive_scheme(self):
48+
assert normalize_server_url("HTTPS://example.glean.com") == "HTTPS://example.glean.com"
49+
assert normalize_server_url("HTTP://localhost") == "HTTP://localhost"
50+
51+
52+
class TestServerURLNormalizerHook:
53+
"""Test cases for the ServerURLNormalizerHook."""
54+
55+
def setup_method(self):
56+
self.hook = ServerURLNormalizerHook()
57+
self.mock_client = Mock(spec=HttpClient)
58+
59+
def test_sdk_init_normalizes_url(self):
60+
result_url, result_client = self.hook.sdk_init("example.glean.com", self.mock_client)
61+
assert result_url == "https://example.glean.com"
62+
assert result_client == self.mock_client
63+
64+
def test_sdk_init_preserves_client(self):
65+
result_url, result_client = self.hook.sdk_init("https://example.glean.com", self.mock_client)
66+
assert result_url == "https://example.glean.com"
67+
assert result_client is self.mock_client
68+
69+
70+
if __name__ == "__main__":
71+
pytest.main([__file__])

0 commit comments

Comments
 (0)