@@ -139,6 +139,7 @@ class AtlanClient(msgspec.Struct, kw_only=True):
139139 _request_params : Any = None
140140 _401_has_retried : Any = None
141141 _user_id : Union [str , None ] = None
142+ _user_client : Any = None
142143 _oauth_token_manager : Any = None
143144 _clients : Any = None # Lazy dict of sub-clients
144145 _caches : Any = None # Lazy dict of caches
@@ -353,6 +354,58 @@ def source_tag_cache(self) -> SourceTagCache:
353354 def dq_template_config_cache (self ) -> DQTemplateConfigCache :
354355 return self ._get_cache ("dq_template_config" , DQTemplateConfigCache )
355356
357+ # --- Class methods ---
358+
359+ @classmethod
360+ def from_token_guid (
361+ cls ,
362+ guid : str ,
363+ base_url : Optional [str ] = None ,
364+ client_id : Optional [str ] = None ,
365+ client_secret : Optional [str ] = None ,
366+ ) -> "AtlanClient" :
367+ from pyatlan .client .common .impersonate import ImpersonateUser
368+ from pyatlan .client .constants import GET_TOKEN
369+ from pyatlan .model .response import AccessTokenResponse
370+
371+ final_base_url = base_url or os .environ .get ("ATLAN_BASE_URL" , "INTERNAL" )
372+ client = cls (base_url = final_base_url , api_key = "" )
373+ client_info = ImpersonateUser .get_client_info (
374+ client_id = client_id , client_secret = client_secret
375+ )
376+ argo_credentials = {
377+ "grant_type" : "client_credentials" ,
378+ "client_id" : client_info .client_id ,
379+ "client_secret" : client_info .client_secret ,
380+ "scope" : "openid" ,
381+ }
382+ from pyatlan .errors import AtlanError , ErrorCode
383+
384+ try :
385+ raw_json = client ._call_api (GET_TOKEN , request_obj = argo_credentials )
386+ argo_token = AccessTokenResponse (** raw_json ).access_token
387+ temp_argo_client = cls (base_url = final_base_url , api_key = argo_token )
388+ except AtlanError as atlan_err :
389+ raise ErrorCode .UNABLE_TO_ESCALATE_WITH_PARAM .exception_with_parameters (
390+ "Failed to obtain Atlan-Argo token"
391+ ) from atlan_err
392+ token_secret = temp_argo_client .impersonate .get_client_secret (client_guid = guid )
393+ token_client_id = temp_argo_client .token .get_by_guid (guid = guid ).client_id
394+ token_credentials = {
395+ "grant_type" : "client_credentials" ,
396+ "client_id" : token_client_id ,
397+ "client_secret" : token_secret ,
398+ "scope" : "openid" ,
399+ }
400+ try :
401+ raw_json = client ._call_api (GET_TOKEN , request_obj = token_credentials )
402+ token_api_key = AccessTokenResponse (** raw_json ).access_token
403+ return cls (base_url = final_base_url , api_key = token_api_key )
404+ except AtlanError as atlan_err :
405+ raise ErrorCode .UNABLE_TO_ESCALATE_WITH_PARAM .exception_with_parameters (
406+ "Failed to obtain access token for API token"
407+ ) from atlan_err
408+
356409 # --- Core API methods ---
357410
358411 def update_headers (self , header : Dict [str , str ]):
@@ -650,6 +703,9 @@ def _create_params(self, api: API, query_params, request_obj):
650703 if request_obj is not None :
651704 if api .consumes == APPLICATION_ENCODED_FORM :
652705 params ["data" ] = request_obj
706+ elif hasattr (request_obj , "json" ) and callable (request_obj .json ):
707+ # Prefer json() method if available (handles nested serialization properly)
708+ params ["data" ] = request_obj .json (by_alias = True , exclude_none = True )
653709 elif hasattr (request_obj , "to_dict" ) and callable (request_obj .to_dict ):
654710 params ["data" ] = json .dumps (request_obj .to_dict ())
655711 elif isinstance (request_obj , (msgspec .Struct , dict , list )):
0 commit comments