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
16 changes: 12 additions & 4 deletions src/glean/api_client/_hooks/x_glean.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
import httpx
from glean.api_client._hooks.types import BeforeRequestContext, BeforeRequestHook

# Header names
EXCLUDE_DEPRECATED_AFTER_HEADER = "X-Glean-Exclude-Deprecated-After"
INCLUDE_EXPERIMENTAL_HEADER = "X-Glean-Include-Experimental"

# Environment variable names
EXCLUDE_DEPRECATED_AFTER_ENV_VAR = "X_GLEAN_EXCLUDE_DEPRECATED_AFTER"
INCLUDE_EXPERIMENTAL_ENV_VAR = "X_GLEAN_INCLUDE_EXPERIMENTAL"


def _get_first_value(
env_value: Optional[str],
Expand Down Expand Up @@ -48,7 +56,7 @@ def before_request(
"""
# Get deprecated value - env var takes precedence
deprecated_value = _get_first_value(
os.environ.get("X_GLEAN_EXCLUDE_DEPRECATED_AFTER"),
os.environ.get(EXCLUDE_DEPRECATED_AFTER_ENV_VAR),
getattr(hook_ctx.config, "exclude_deprecated_after", None),
)

Expand All @@ -57,14 +65,14 @@ def before_request(
"true" if getattr(hook_ctx.config, "include_experimental", None) is True else None
)
experimental_value = _get_first_value(
os.environ.get("X_GLEAN_INCLUDE_EXPERIMENTAL"),
os.environ.get(INCLUDE_EXPERIMENTAL_ENV_VAR),
config_experimental,
)

if deprecated_value:
request.headers["X-Glean-Exclude-Deprecated-After"] = deprecated_value
request.headers[EXCLUDE_DEPRECATED_AFTER_HEADER] = deprecated_value

if experimental_value:
request.headers["X-Glean-Include-Experimental"] = experimental_value
request.headers[INCLUDE_EXPERIMENTAL_HEADER] = experimental_value

return request
76 changes: 41 additions & 35 deletions tests/test_x_glean_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@
import httpx
import pytest

from src.glean.api_client._hooks.x_glean import XGlean
from src.glean.api_client._hooks.x_glean import (
XGlean,
EXCLUDE_DEPRECATED_AFTER_HEADER,
INCLUDE_EXPERIMENTAL_HEADER,
EXCLUDE_DEPRECATED_AFTER_ENV_VAR,
INCLUDE_EXPERIMENTAL_ENV_VAR,
)
from src.glean.api_client._hooks.types import BeforeRequestContext, HookContext
from src.glean.api_client.sdkconfiguration import SDKConfiguration

Expand Down Expand Up @@ -43,25 +49,25 @@ class TestXGleanHook:
def clear_env_vars(self):
"""Clear X-Glean environment variables before and after each test."""
# Store original values
original_deprecated = os.environ.get("X_GLEAN_EXCLUDE_DEPRECATED_AFTER")
original_experimental = os.environ.get("X_GLEAN_INCLUDE_EXPERIMENTAL")
original_deprecated = os.environ.get(EXCLUDE_DEPRECATED_AFTER_ENV_VAR)
original_experimental = os.environ.get(INCLUDE_EXPERIMENTAL_ENV_VAR)

# Clear for test
os.environ.pop("X_GLEAN_EXCLUDE_DEPRECATED_AFTER", None)
os.environ.pop("X_GLEAN_INCLUDE_EXPERIMENTAL", None)
os.environ.pop(EXCLUDE_DEPRECATED_AFTER_ENV_VAR, None)
os.environ.pop(INCLUDE_EXPERIMENTAL_ENV_VAR, None)

yield

# Restore original values
if original_deprecated is not None:
os.environ["X_GLEAN_EXCLUDE_DEPRECATED_AFTER"] = original_deprecated
os.environ[EXCLUDE_DEPRECATED_AFTER_ENV_VAR] = original_deprecated
else:
os.environ.pop("X_GLEAN_EXCLUDE_DEPRECATED_AFTER", None)
os.environ.pop(EXCLUDE_DEPRECATED_AFTER_ENV_VAR, None)

if original_experimental is not None:
os.environ["X_GLEAN_INCLUDE_EXPERIMENTAL"] = original_experimental
os.environ[INCLUDE_EXPERIMENTAL_ENV_VAR] = original_experimental
else:
os.environ.pop("X_GLEAN_INCLUDE_EXPERIMENTAL", None)
os.environ.pop(INCLUDE_EXPERIMENTAL_ENV_VAR, None)

def test_no_headers_when_neither_options_nor_env_vars_set(self):
"""Should not set any X-Glean headers when nothing is configured."""
Expand All @@ -71,8 +77,8 @@ def test_no_headers_when_neither_options_nor_env_vars_set(self):

result = hook.before_request(context, request)

assert "X-Glean-Exclude-Deprecated-After" not in result.headers
assert "X-Glean-Include-Experimental" not in result.headers
assert EXCLUDE_DEPRECATED_AFTER_HEADER not in result.headers
assert INCLUDE_EXPERIMENTAL_HEADER not in result.headers

def test_sets_deprecated_header_from_sdk_option(self):
"""Should set X-Glean-Exclude-Deprecated-After header from SDK option."""
Expand All @@ -82,7 +88,7 @@ def test_sets_deprecated_header_from_sdk_option(self):

result = hook.before_request(context, request)

assert result.headers.get("X-Glean-Exclude-Deprecated-After") == "2026-10-15"
assert result.headers.get(EXCLUDE_DEPRECATED_AFTER_HEADER) == "2026-10-15"

def test_sets_experimental_header_when_include_experimental_is_true(self):
"""Should set X-Glean-Include-Experimental header when includeExperimental is True."""
Expand All @@ -92,7 +98,7 @@ def test_sets_experimental_header_when_include_experimental_is_true(self):

result = hook.before_request(context, request)

assert result.headers.get("X-Glean-Include-Experimental") == "true"
assert result.headers.get(INCLUDE_EXPERIMENTAL_HEADER) == "true"

def test_no_experimental_header_when_include_experimental_is_false(self):
"""Should not set X-Glean-Include-Experimental header when includeExperimental is False."""
Expand All @@ -102,7 +108,7 @@ def test_no_experimental_header_when_include_experimental_is_false(self):

result = hook.before_request(context, request)

assert "X-Glean-Include-Experimental" not in result.headers
assert INCLUDE_EXPERIMENTAL_HEADER not in result.headers

def test_sets_both_headers_when_both_options_provided(self):
"""Should set both headers when both options are provided."""
Expand All @@ -115,75 +121,75 @@ def test_sets_both_headers_when_both_options_provided(self):

result = hook.before_request(context, request)

assert result.headers.get("X-Glean-Exclude-Deprecated-After") == "2026-10-15"
assert result.headers.get("X-Glean-Include-Experimental") == "true"
assert result.headers.get(EXCLUDE_DEPRECATED_AFTER_HEADER) == "2026-10-15"
assert result.headers.get(INCLUDE_EXPERIMENTAL_HEADER) == "true"

def test_sets_deprecated_header_from_env_var(self):
"""Should set X-Glean-Exclude-Deprecated-After header from environment variable."""
os.environ["X_GLEAN_EXCLUDE_DEPRECATED_AFTER"] = "2027-01-01"
os.environ[EXCLUDE_DEPRECATED_AFTER_ENV_VAR] = "2027-01-01"

hook = XGlean()
request = create_mock_request()
context = create_mock_context()

result = hook.before_request(context, request)

assert result.headers.get("X-Glean-Exclude-Deprecated-After") == "2027-01-01"
assert result.headers.get(EXCLUDE_DEPRECATED_AFTER_HEADER) == "2027-01-01"

def test_sets_experimental_header_from_env_var(self):
"""Should set X-Glean-Include-Experimental header from environment variable."""
os.environ["X_GLEAN_INCLUDE_EXPERIMENTAL"] = "true"
os.environ[INCLUDE_EXPERIMENTAL_ENV_VAR] = "true"

hook = XGlean()
request = create_mock_request()
context = create_mock_context()

result = hook.before_request(context, request)

assert result.headers.get("X-Glean-Include-Experimental") == "true"
assert result.headers.get(INCLUDE_EXPERIMENTAL_HEADER) == "true"

def test_sets_both_headers_from_env_vars(self):
"""Should set both headers from environment variables."""
os.environ["X_GLEAN_EXCLUDE_DEPRECATED_AFTER"] = "2027-06-15"
os.environ["X_GLEAN_INCLUDE_EXPERIMENTAL"] = "true"
os.environ[EXCLUDE_DEPRECATED_AFTER_ENV_VAR] = "2027-06-15"
os.environ[INCLUDE_EXPERIMENTAL_ENV_VAR] = "true"

hook = XGlean()
request = create_mock_request()
context = create_mock_context()

result = hook.before_request(context, request)

assert result.headers.get("X-Glean-Exclude-Deprecated-After") == "2027-06-15"
assert result.headers.get("X-Glean-Include-Experimental") == "true"
assert result.headers.get(EXCLUDE_DEPRECATED_AFTER_HEADER) == "2027-06-15"
assert result.headers.get(INCLUDE_EXPERIMENTAL_HEADER) == "true"

def test_env_var_takes_precedence_for_deprecated(self):
"""Environment variable should take precedence over SDK option for deprecated."""
os.environ["X_GLEAN_EXCLUDE_DEPRECATED_AFTER"] = "2027-12-31"
os.environ[EXCLUDE_DEPRECATED_AFTER_ENV_VAR] = "2027-12-31"

hook = XGlean()
request = create_mock_request()
context = create_mock_context(exclude_deprecated_after="2026-01-01")

result = hook.before_request(context, request)

assert result.headers.get("X-Glean-Exclude-Deprecated-After") == "2027-12-31"
assert result.headers.get(EXCLUDE_DEPRECATED_AFTER_HEADER) == "2027-12-31"

def test_env_var_takes_precedence_for_experimental(self):
"""Environment variable should take precedence over SDK option for experimental."""
os.environ["X_GLEAN_INCLUDE_EXPERIMENTAL"] = "true"
os.environ[INCLUDE_EXPERIMENTAL_ENV_VAR] = "true"

hook = XGlean()
request = create_mock_request()
context = create_mock_context(include_experimental=False)

result = hook.before_request(context, request)

assert result.headers.get("X-Glean-Include-Experimental") == "true"
assert result.headers.get(INCLUDE_EXPERIMENTAL_HEADER) == "true"

def test_env_vars_take_precedence_for_both_headers(self):
"""Environment variables should take precedence for both headers when all are set."""
os.environ["X_GLEAN_EXCLUDE_DEPRECATED_AFTER"] = "2028-01-01"
os.environ["X_GLEAN_INCLUDE_EXPERIMENTAL"] = "true"
os.environ[EXCLUDE_DEPRECATED_AFTER_ENV_VAR] = "2028-01-01"
os.environ[INCLUDE_EXPERIMENTAL_ENV_VAR] = "true"

hook = XGlean()
request = create_mock_request()
Expand All @@ -194,8 +200,8 @@ def test_env_vars_take_precedence_for_both_headers(self):

result = hook.before_request(context, request)

assert result.headers.get("X-Glean-Exclude-Deprecated-After") == "2028-01-01"
assert result.headers.get("X-Glean-Include-Experimental") == "true"
assert result.headers.get(EXCLUDE_DEPRECATED_AFTER_HEADER) == "2028-01-01"
assert result.headers.get(INCLUDE_EXPERIMENTAL_HEADER) == "true"

def test_preserves_existing_headers(self):
"""Should preserve existing headers when adding X-Glean headers."""
Expand All @@ -214,8 +220,8 @@ def test_preserves_existing_headers(self):

assert result.headers.get("Authorization") == "Bearer token"
assert result.headers.get("Content-Type") == "application/json"
assert result.headers.get("X-Glean-Exclude-Deprecated-After") == "2026-10-15"
assert result.headers.get("X-Glean-Include-Experimental") == "true"
assert result.headers.get(EXCLUDE_DEPRECATED_AFTER_HEADER) == "2026-10-15"
assert result.headers.get(INCLUDE_EXPERIMENTAL_HEADER) == "true"

def test_returns_httpx_request_instance(self):
"""Should return an httpx.Request instance."""
Expand Down Expand Up @@ -257,7 +263,7 @@ def test_handles_multipart_streaming_request(self):

result = hook.before_request(context, request)

assert result.headers.get("X-Glean-Include-Experimental") == "true"
assert result.headers.get(INCLUDE_EXPERIMENTAL_HEADER) == "true"
assert result.method == "POST"


Expand Down
Loading