148148
149149HEADER_AUTHORIZATION_KEY = "Authorization"
150150HEADER_SNOWFLAKE_TOKEN = 'Snowflake Token="{token}"'
151+ HEADER_EXTERNAL_SESSION_KEY = "X-Snowflake-External-Session-ID"
151152
152153REQUEST_ID = "requestId"
153154REQUEST_GUID = "request_guid"
154155SNOWFLAKE_HOST_SUFFIX = ".snowflakecomputing.com"
155156
156-
157157SNOWFLAKE_CONNECTOR_VERSION = SNOWFLAKE_CONNECTOR_VERSION
158158PYTHON_VERSION = PYTHON_VERSION
159159OPERATING_SYSTEM = OPERATING_SYSTEM
166166PYTHON_CONNECTOR_USER_AGENT = f"{ CLIENT_NAME } /{ SNOWFLAKE_CONNECTOR_VERSION } ({ PLATFORM } ) { IMPLEMENTATION } /{ PYTHON_VERSION } "
167167
168168NO_TOKEN = "no-token"
169+ NO_EXTERNAL_SESSION_ID = "no-external-session-id"
169170
170171STATUS_TO_EXCEPTION : dict [int , type [Error ]] = {
171172 INTERNAL_SERVER_ERROR : InternalServerError ,
189190PROGRAMMATIC_ACCESS_TOKEN = "PROGRAMMATIC_ACCESS_TOKEN"
190191NO_AUTH_AUTHENTICATOR = "NO_AUTH"
191192WORKLOAD_IDENTITY_AUTHENTICATOR = "WORKLOAD_IDENTITY"
193+ PAT_WITH_EXTERNAL_SESSION = "PAT_WITH_EXTERNAL_SESSION"
192194
193195
194196def is_retryable_http_code (code : int ) -> bool :
@@ -313,6 +315,25 @@ def __call__(self, r: PreparedRequest) -> PreparedRequest:
313315 return r
314316
315317
318+ class PATWithExternalSessionAuth (AuthBase ):
319+ """Attaches HTTP Authorization headers for PAT with External Session."""
320+
321+ def __init__ (self , token , external_session_id ) -> None :
322+ # setup any auth-related data here
323+ self .token = token
324+ self .external_session_id = external_session_id
325+
326+ def __call__ (self , r : PreparedRequest ) -> PreparedRequest :
327+ """Modifies and returns the request."""
328+ if HEADER_AUTHORIZATION_KEY in r .headers :
329+ del r .headers [HEADER_AUTHORIZATION_KEY ]
330+ if self .token != NO_TOKEN :
331+ r .headers [HEADER_AUTHORIZATION_KEY ] = "Bearer " + self .token
332+ if self .external_session_id != NO_EXTERNAL_SESSION_ID :
333+ r .headers [HEADER_EXTERNAL_SESSION_KEY ] = self .external_session_id
334+ return r
335+
336+
316337class SessionPool :
317338 def __init__ (self , rest : SnowflakeRestful ) -> None :
318339 # A stack of the idle sessions
@@ -404,6 +425,12 @@ def __init__(
404425 def token (self ) -> str | None :
405426 return self ._token if hasattr (self , "_token" ) else None
406427
428+ @property
429+ def external_session_id (self ) -> str | None :
430+ return (
431+ self ._external_session_id if hasattr (self , "_external_session_id" ) else None
432+ )
433+
407434 @property
408435 def master_token (self ) -> str | None :
409436 return self ._master_token if hasattr (self , "_master_token" ) else None
@@ -513,6 +540,7 @@ def request(
513540 headers ,
514541 json .dumps (body , cls = SnowflakeRestfulJsonEncoder ),
515542 token = self .token ,
543+ external_session_id = self .external_session_id ,
516544 _no_results = _no_results ,
517545 timeout = timeout ,
518546 _include_retry_params = _include_retry_params ,
@@ -523,6 +551,7 @@ def request(
523551 url ,
524552 headers ,
525553 token = self .token ,
554+ external_session_id = self .external_session_id ,
526555 timeout = timeout ,
527556 )
528557
@@ -542,6 +571,17 @@ def update_tokens(
542571 self ._mfa_token = mfa_token
543572 self ._master_validity_in_seconds = master_validity_in_seconds
544573
574+ def set_pat_and_external_session (
575+ self ,
576+ personal_access_token ,
577+ external_session_id ,
578+ ) -> None :
579+ """Updates session and master tokens and optionally temporary credential."""
580+ with self ._lock_token :
581+ self ._personal_access_token = personal_access_token
582+ self ._token = personal_access_token
583+ self ._external_session_id = external_session_id
584+
545585 def _renew_session (self ):
546586 """Renew a session and master token."""
547587 return self ._token_request (REQUEST_TYPE_RENEW )
@@ -698,6 +738,7 @@ def _get_request(
698738 url : str ,
699739 headers : dict [str , str ],
700740 token : str = None ,
741+ external_session_id : str = None ,
701742 timeout : int | None = None ,
702743 is_fetch_query_status : bool = False ,
703744 ) -> dict [str , Any ]:
@@ -713,9 +754,13 @@ def _get_request(
713754 headers ,
714755 timeout = timeout ,
715756 token = token ,
757+ external_session_id = external_session_id ,
716758 is_fetch_query_status = is_fetch_query_status ,
717759 )
718- if ret .get ("code" ) == SESSION_EXPIRED_GS_CODE :
760+ if (
761+ ret .get ("code" ) == SESSION_EXPIRED_GS_CODE
762+ and self ._connection ._authenticator != PAT_WITH_EXTERNAL_SESSION
763+ ):
719764 try :
720765 ret = self ._renew_session ()
721766 except ReauthenticationRequest as ex :
@@ -743,6 +788,7 @@ def _post_request(
743788 headers ,
744789 body ,
745790 token = None ,
791+ external_session_id : str | None = None ,
746792 timeout : int | None = None ,
747793 socket_timeout : int | None = None ,
748794 _no_results : bool = False ,
@@ -763,6 +809,7 @@ def _post_request(
763809 data = body ,
764810 timeout = timeout ,
765811 token = token ,
812+ external_session_id = external_session_id ,
766813 no_retry = no_retry ,
767814 _include_retry_params = _include_retry_params ,
768815 socket_timeout = socket_timeout ,
@@ -775,7 +822,10 @@ def _post_request(
775822
776823 if ret .get ("code" ) == MASTER_TOKEN_EXPIRED_GS_CODE :
777824 self ._connection .expired = True
778- elif ret .get ("code" ) == SESSION_EXPIRED_GS_CODE :
825+ elif (
826+ ret .get ("code" ) == SESSION_EXPIRED_GS_CODE
827+ and self ._connection ._authenticator != PAT_WITH_EXTERNAL_SESSION
828+ ):
779829 try :
780830 ret = self ._renew_session ()
781831 except ReauthenticationRequest as ex :
@@ -900,6 +950,7 @@ def _request_exec_wrapper(
900950 retry_ctx ,
901951 no_retry : bool = False ,
902952 token = NO_TOKEN ,
953+ external_session_id = NO_EXTERNAL_SESSION_ID ,
903954 ** kwargs ,
904955 ):
905956 conn = self ._connection
@@ -928,6 +979,7 @@ def _request_exec_wrapper(
928979 headers = headers ,
929980 data = data ,
930981 token = token ,
982+ external_session_id = external_session_id ,
931983 raise_raw_http_failure = raise_raw_http_failure ,
932984 ** kwargs ,
933985 )
@@ -1061,6 +1113,7 @@ def _request_exec(
10611113 headers ,
10621114 data ,
10631115 token ,
1116+ external_session_id = None ,
10641117 catch_okta_unauthorized_error : bool = False ,
10651118 is_raw_text : bool = False ,
10661119 is_raw_binary : bool = False ,
@@ -1088,6 +1141,11 @@ def _request_exec(
10881141 # socket timeout is constant. You should be able to receive
10891142 # the response within the time. If not, ConnectReadTimeout or
10901143 # ReadTimeout is raised.
1144+ auth = (
1145+ PATWithExternalSessionAuth (token , external_session_id )
1146+ if (external_session_id is not None and token is not None )
1147+ else SnowflakeAuth (token )
1148+ )
10911149 raw_ret = session .request (
10921150 method = method ,
10931151 url = full_url ,
@@ -1096,7 +1154,7 @@ def _request_exec(
10961154 timeout = socket_timeout ,
10971155 verify = True ,
10981156 stream = is_raw_binary ,
1099- auth = SnowflakeAuth ( token ) ,
1157+ auth = auth ,
11001158 )
11011159 download_end_time = get_time_millis ()
11021160
0 commit comments