44from urllib .request import getproxies
55
66import semver
7- from aignx .codegen .api .public_api import PublicApi
87from aignx .codegen .api_client import ApiClient
9- from aignx .codegen .configuration import AuthSettings , Configuration
108from aignx .codegen .exceptions import NotFoundException , ServiceException
119from aignx .codegen .models import ApplicationReadResponse as Application
1210from aignx .codegen .models import MeReadResponse as Me
2220from urllib3 .exceptions import IncompleteRead , PoolError , ProtocolError , ProxyError
2321from urllib3 .exceptions import TimeoutError as Urllib3TimeoutError
2422
23+ from aignostics .platform ._api import _AuthenticatedApi , _OAuth2TokenProviderConfiguration
2524from aignostics .platform ._authentication import get_token
2625from aignostics .platform ._operation_cache import cached_operation
2726from aignostics .platform .resources .applications import Applications , Versions
@@ -59,34 +58,6 @@ def _log_retry_attempt(retry_state: RetryCallState) -> None:
5958 )
6059
6160
62- class _OAuth2TokenProviderConfiguration (Configuration ):
63- """
64- Overwrites the original Configuration to call a function to obtain a refresh token.
65-
66- The base class does not support callbacks. This is necessary for integrations where
67- tokens may expire or need to be refreshed automatically.
68- """
69-
70- def __init__ (
71- self , host : str , ssl_ca_cert : str | None = None , token_provider : Callable [[], str ] | None = None
72- ) -> None :
73- super ().__init__ (host = host , ssl_ca_cert = ssl_ca_cert )
74- self .token_provider = token_provider
75-
76- def auth_settings (self ) -> AuthSettings :
77- token = self .token_provider () if self .token_provider else None
78- if not token :
79- return {}
80- return {
81- "OAuth2AuthorizationCodeBearer" : {
82- "type" : "oauth2" ,
83- "in" : "header" ,
84- "key" : "Authorization" ,
85- "value" : f"Bearer { token } " ,
86- }
87- }
88-
89-
9061class Client :
9162 """Main client for interacting with the Aignostics Platform API.
9263
@@ -96,25 +67,41 @@ class Client:
9667 - Caches operation results for specific operations.
9768 """
9869
99- _api_client_cached : ClassVar [PublicApi | None ] = None
100- _api_client_uncached : ClassVar [PublicApi | None ] = None
70+ _api_client_cached : ClassVar [_AuthenticatedApi | None ] = None
71+ _api_client_uncached : ClassVar [_AuthenticatedApi | None ] = None
72+ _api_client_external : ClassVar [dict [int , _AuthenticatedApi ]] = {}
10173
74+ _api : _AuthenticatedApi
10275 applications : Applications
10376 versions : Versions
10477 runs : Runs
10578
106- def __init__ (self , cache_token : bool = True ) -> None :
79+ def __init__ (self , cache_token : bool = True , token_provider : Callable [[], str ] | None = None ) -> None :
10780 """Initializes a client instance with authenticated API access.
10881
10982 Args:
110- cache_token (bool): If True, caches the authentication token.
111- Defaults to True.
83+ cache_token: If True, caches the authentication token. Defaults to True.
84+ token_provider: Optional external token provider callable. When provided,
85+ bypasses internal OAuth authentication entirely. The callable must
86+ return a valid bearer token string.
87+
88+ Raises:
89+ ValueError: If both ``token_provider`` and ``cache_token=False`` are specified,
90+ since token caching is irrelevant when using an external provider.
11291
11392 Sets up resource accessors for applications, versions, and runs.
11493 """
94+ if token_provider is not None and not cache_token :
95+ msg = (
96+ "Cannot set cache_token=False with an external token_provider. "
97+ "Token caching is managed internally when using the default OAuth flow. "
98+ "When providing an external token_provider, omit cache_token or use the default."
99+ )
100+ raise ValueError (msg )
101+
115102 try :
116- logger .trace ("Initializing client with cache_token={}" , cache_token )
117- self ._api = Client .get_api_client (cache_token = cache_token )
103+ logger .trace ("Initializing client with cache_token={}, token_provider={} " , cache_token , token_provider )
104+ self ._api = Client .get_api_client (cache_token = cache_token , token_provider = token_provider )
118105 self .applications : Applications = Applications (self ._api )
119106 self .runs : Runs = Runs (self ._api )
120107 self .versions : Versions = Versions (self ._api )
@@ -143,7 +130,7 @@ def me(self, nocache: bool = False) -> Me:
143130 aignx.codegen.exceptions.ApiException: If the API call fails.
144131 """
145132
146- @cached_operation (ttl = settings ().me_cache_ttl , use_token = True )
133+ @cached_operation (ttl = settings ().me_cache_ttl , token_provider = self . _api . token_provider )
147134 def me_with_retry () -> Me :
148135 return Retrying ( # We are not using Tenacity annotations as settings can change at runtime
149136 retry = retry_if_exception_type (exception_types = RETRYABLE_EXCEPTIONS ),
@@ -177,7 +164,7 @@ def application(self, application_id: str, nocache: bool = False) -> Application
177164 Application: The application object.
178165 """
179166
180- @cached_operation (ttl = settings ().application_cache_ttl , use_token = True )
167+ @cached_operation (ttl = settings ().application_cache_ttl , token_provider = self . _api . token_provider )
181168 def application_with_retry (application_id : str ) -> Application :
182169 return Retrying (
183170 retry = retry_if_exception_type (exception_types = RETRYABLE_EXCEPTIONS ),
@@ -234,7 +221,7 @@ def application_version(
234221 raise ValueError (message )
235222
236223 # Make the API call with retry logic and caching
237- @cached_operation (ttl = settings ().application_version_cache_ttl , use_token = True )
224+ @cached_operation (ttl = settings ().application_version_cache_ttl , token_provider = self . _api . token_provider )
238225 def application_version_with_retry (application_id : str , version : str ) -> ApplicationVersion :
239226 return Retrying (
240227 retry = retry_if_exception_type (exception_types = RETRYABLE_EXCEPTIONS ),
@@ -268,44 +255,53 @@ def run(self, run_id: str) -> Run:
268255 return Run (self ._api , run_id )
269256
270257 @staticmethod
271- def get_api_client (cache_token : bool = True ) -> PublicApi :
258+ def get_api_client (cache_token : bool = True , token_provider : Callable [[], str ] | None = None ) -> _AuthenticatedApi :
272259 """Create and configure an authenticated API client.
273260
274261 API client instances are shared across all Client instances for efficient connection reuse.
275- Two separate instances are maintained: one for cached tokens and one for uncached tokens.
262+ Three pools are maintained: cached-token, uncached-token, and external-provider (keyed by
263+ provider identity).
276264
277265 Args:
278- cache_token (bool): If True, caches the authentication token.
279- Defaults to True.
266+ cache_token: If True, caches the authentication token. Defaults to True.
267+ token_provider: Optional external token provider. When provided, bypasses
268+ internal OAuth and uses this callable to obtain bearer tokens.
280269
281270 Returns:
282- PublicApi : Configured API client with authentication token.
271+ _AuthenticatedApi : Configured API client with authentication token.
283272
284273 Raises:
285274 RuntimeError: If authentication fails.
286275 """
287- # Return cached instance if available
288- if cache_token and Client ._api_client_cached is not None :
276+ # Check singleton caches first
277+ if token_provider is not None :
278+ provider_key = id (token_provider )
279+ if provider_key in Client ._api_client_external :
280+ return Client ._api_client_external [provider_key ]
281+ elif cache_token and Client ._api_client_cached is not None :
289282 return Client ._api_client_cached
290- if not cache_token and Client ._api_client_uncached is not None :
283+ elif not cache_token and Client ._api_client_uncached is not None :
291284 return Client ._api_client_uncached
292285
293- def token_provider () -> str :
294- return get_token (use_cache = cache_token )
286+ # Resolve the effective token provider
287+ effective_provider : Callable [[], str ] = (
288+ token_provider if token_provider is not None else (lambda : get_token (use_cache = cache_token ))
289+ )
295290
291+ # Build the API client
296292 ca_file = os .getenv ("REQUESTS_CA_BUNDLE" ) # point to .cer file of proxy if defined
297293 config = _OAuth2TokenProviderConfiguration (
298- host = settings ().api_root , ssl_ca_cert = ca_file , token_provider = token_provider
294+ host = settings ().api_root , ssl_ca_cert = ca_file , token_provider = effective_provider
299295 )
300296 config .proxy = getproxies ().get ("https" ) # use system proxy
301- client = ApiClient (
302- config ,
303- )
297+ client = ApiClient (config )
304298 client .user_agent = user_agent ()
305- api_client = PublicApi (client )
299+ api_client = _AuthenticatedApi (client , effective_provider )
306300
307- # Cache the instance
308- if cache_token :
301+ # Store in the appropriate singleton cache
302+ if token_provider is not None :
303+ Client ._api_client_external [provider_key ] = api_client
304+ elif cache_token :
309305 Client ._api_client_cached = api_client
310306 else :
311307 Client ._api_client_uncached = api_client
0 commit comments