Skip to content

Commit a4328a3

Browse files
fordNclaude
andcommitted
feat(registry): Add auto-refreshing auth token support
Update RegistryClient to support the same auth patterns as AdminClient: Before: - Static auth token set once at initialization in HTTP headers - No token refresh capability - would expire after ~1 hour After: - Auth token provider stored as `_get_token` callable - Token header added dynamically per-request (like AdminClient) - Supports auth priority: auth_token > AMP_AUTH_TOKEN > auth=True - Auto-refreshes when using auth=True with AuthService Changes: - Add `auth` parameter to RegistryClient.__init__() - Store `_get_token` callable instead of static token - Add auth header in `_request()` method per-request - Update Client to pass `auth=True` to RegistryClient (not static token) - Both AdminClient and RegistryClient now have consistent auth handling This ensures long-running processes using RegistryClient won't fail when the initial token expires. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent a671520 commit a4328a3

File tree

3 files changed

+69
-20
lines changed

3 files changed

+69
-20
lines changed

src/amp/client.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -357,12 +357,13 @@ def get_token():
357357
if admin_url:
358358
from amp.admin.client import AdminClient
359359

360-
# Pass through the same auth configuration to maintain consistent behavior
360+
# Pass through auth parameters to AdminClient so it can set up its own auth
361+
# (AdminClient needs to manage its own AuthService for token refresh)
361362
if auth:
362-
# Use auth file (auto-refreshing via AuthService)
363+
# Use auth file - AdminClient will set up AuthService for auto-refresh
363364
self._admin_client = AdminClient(admin_url, auth=True)
364365
elif auth_token or os.getenv('AMP_AUTH_TOKEN'):
365-
# Use static token (explicit param takes priority over env var)
366+
# Use static token (explicit param takes priority)
366367
token = auth_token or os.getenv('AMP_AUTH_TOKEN')
367368
self._admin_client = AdminClient(admin_url, auth_token=token)
368369
else:
@@ -375,12 +376,17 @@ def get_token():
375376
if registry_url:
376377
from amp.registry import RegistryClient
377378

378-
# Use the same auth approach - get current token value
379-
# Registry client doesn't call APIs per-request, so static token is fine
380-
if get_token:
381-
current_token = get_token()
382-
self._registry_client = RegistryClient(registry_url, auth_token=current_token)
379+
# Pass through auth parameters to RegistryClient so it can set up its own auth
380+
# (RegistryClient needs to manage its own AuthService for token refresh)
381+
if auth:
382+
# Use auth file - RegistryClient will set up AuthService for auto-refresh
383+
self._registry_client = RegistryClient(registry_url, auth=True)
384+
elif auth_token or os.getenv('AMP_AUTH_TOKEN'):
385+
# Use static token (explicit param takes priority)
386+
token = auth_token or os.getenv('AMP_AUTH_TOKEN')
387+
self._registry_client = RegistryClient(registry_url, auth_token=token)
383388
else:
389+
# No authentication
384390
self._registry_client = RegistryClient(registry_url)
385391
else:
386392
self._registry_client = None

src/amp/registry/client.py

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Registry API client."""
22

33
import logging
4+
import os
45
from typing import Optional
56

67
import httpx
@@ -17,44 +18,79 @@ class RegistryClient:
1718
1819
Args:
1920
base_url: Base URL for the Registry API (default: staging registry)
20-
auth_token: Optional Bearer token for authenticated operations
21+
auth_token: Optional Bearer token for authenticated operations (highest priority)
22+
auth: If True, load auth token from ~/.amp/cache (shared with TS CLI)
23+
24+
Authentication Priority (highest to lowest):
25+
1. Explicit auth_token parameter
26+
2. AMP_AUTH_TOKEN environment variable
27+
3. auth=True - reads from ~/.amp/cache/amp_cli_auth
2128
2229
Example:
2330
>>> # Read-only operations (no auth required)
2431
>>> client = RegistryClient()
2532
>>> datasets = client.datasets.search('ethereum')
2633
>>>
27-
>>> # Authenticated operations (publishing, etc.)
34+
>>> # Authenticated operations with explicit token
2835
>>> client = RegistryClient(auth_token='your-token')
2936
>>> client.datasets.publish(...)
37+
>>>
38+
>>> # Authenticated operations with auth file (auto-refresh)
39+
>>> client = RegistryClient(auth=True)
40+
>>> client.datasets.publish(...)
3041
"""
3142

3243
def __init__(
3344
self,
3445
base_url: str = 'https://api.registry.amp.staging.thegraph.com',
3546
auth_token: Optional[str] = None,
47+
auth: bool = False,
3648
):
3749
"""Initialize Registry client.
3850
3951
Args:
4052
base_url: Base URL for the Registry API
4153
auth_token: Optional Bearer token for authentication
54+
auth: If True, load auth token from ~/.amp/cache
55+
56+
Raises:
57+
ValueError: If both auth=True and auth_token are provided
4258
"""
59+
if auth and auth_token:
60+
raise ValueError('Cannot specify both auth=True and auth_token. Choose one authentication method.')
61+
4362
self.base_url = base_url.rstrip('/')
44-
self.auth_token = auth_token
4563

46-
# Build headers
47-
headers = {
48-
'Content-Type': 'application/json',
49-
'Accept': 'application/json',
50-
}
64+
# Resolve auth token provider with priority: explicit param > env var > auth file
65+
self._get_token = None
5166
if auth_token:
52-
headers['Authorization'] = f'Bearer {auth_token}'
67+
# Priority 1: Explicit auth_token parameter (static token)
68+
def get_token():
69+
return auth_token
5370

54-
# Create HTTP client
71+
self._get_token = get_token
72+
elif os.getenv('AMP_AUTH_TOKEN'):
73+
# Priority 2: AMP_AUTH_TOKEN environment variable (static token)
74+
env_token = os.getenv('AMP_AUTH_TOKEN')
75+
76+
def get_token():
77+
return env_token
78+
79+
self._get_token = get_token
80+
elif auth:
81+
# Priority 3: Load from ~/.amp/cache/amp_cli_auth (auto-refreshing)
82+
from amp.auth import AuthService
83+
84+
auth_service = AuthService()
85+
self._get_token = auth_service.get_token # Callable that auto-refreshes
86+
87+
# Create HTTP client (no auth header yet - will be added per-request)
5588
self._http = httpx.Client(
5689
base_url=self.base_url,
57-
headers=headers,
90+
headers={
91+
'Content-Type': 'application/json',
92+
'Accept': 'application/json',
93+
},
5894
timeout=30.0,
5995
)
6096

@@ -92,6 +128,12 @@ def _request(
92128
"""
93129
url = path if path.startswith('http') else f'{self.base_url}{path}'
94130

131+
# Add auth header dynamically (auto-refreshes if needed)
132+
headers = kwargs.get('headers', {})
133+
if self._get_token:
134+
headers['Authorization'] = f'Bearer {self._get_token()}'
135+
kwargs['headers'] = headers
136+
95137
try:
96138
response = self._http.request(method, url, **kwargs)
97139

tests/unit/test_client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ def getenv_side_effect(key, default=None):
149149
Client(query_url='grpc://localhost:1602', auth=True)
150150

151151
# Verify auth file was used
152-
mock_auth_service.assert_called_once()
152+
# Note: AuthService is called twice - once for Flight SQL, once for RegistryClient
153+
assert mock_auth_service.call_count == 2
153154
mock_connect.assert_called_once()
154155
call_args = mock_connect.call_args
155156
middleware = call_args[1].get('middleware', [])

0 commit comments

Comments
 (0)