11from __future__ import annotations
22
33import os
4- from typing import Iterable , Optional
4+ from typing import Iterable
55
66import requests
77
@@ -27,15 +27,16 @@ class DescopeClient:
2727 def __init__ (
2828 self ,
2929 project_id : str ,
30- public_key : Optional [ dict ] = None ,
30+ public_key : dict | None = None ,
3131 skip_verify : bool = False ,
32- management_key : Optional [ str ] = None ,
32+ management_key : str | None = None ,
3333 timeout_seconds : float = DEFAULT_TIMEOUT_SECONDS ,
3434 jwt_validation_leeway : int = 5 ,
35- auth_management_key : Optional [ str ] = None ,
36- fga_cache_url : Optional [ str ] = None ,
35+ auth_management_key : str | None = None ,
36+ fga_cache_url : str | None = None ,
3737 * ,
38- base_url : Optional [str ] = None ,
38+ base_url : str | None = None ,
39+ verbose : bool = False ,
3940 ):
4041 # validate project id
4142 project_id = project_id or os .getenv ("DESCOPE_PROJECT_ID" , "" )
@@ -57,6 +58,7 @@ def __init__(
5758 secure = not skip_verify ,
5859 management_key = auth_management_key
5960 or os .getenv ("DESCOPE_AUTH_MANAGEMENT_KEY" ),
61+ verbose = verbose ,
6062 )
6163 self ._auth = Auth (
6264 project_id ,
@@ -81,13 +83,18 @@ def __init__(
8183 timeout_seconds = auth_http_client .timeout_seconds ,
8284 secure = auth_http_client .secure ,
8385 management_key = management_key or os .getenv ("DESCOPE_MANAGEMENT_KEY" ),
86+ verbose = verbose ,
8487 )
8588 self ._mgmt = MGMT (
8689 http_client = mgmt_http_client ,
8790 auth = self ._auth ,
8891 fga_cache_url = fga_cache_url ,
8992 )
9093
94+ # Store references to HTTP clients for verbose mode access
95+ self ._auth_http_client = auth_http_client
96+ self ._mgmt_http_client = mgmt_http_client
97+
9198 @property
9299 def mgmt (self ):
93100 return self ._mgmt
@@ -328,7 +335,7 @@ def get_matched_tenant_roles(
328335 return matched
329336
330337 def validate_session (
331- self , session_token : str , audience : Optional [ Iterable [str ] | str ] = None
338+ self , session_token : str , audience : Iterable [str ] | str | None = None
332339 ) -> dict :
333340 """
334341 Validate a session token. Call this function for every incoming request to your
@@ -351,7 +358,7 @@ def validate_session(
351358 return self ._auth .validate_session (session_token , audience )
352359
353360 def refresh_session (
354- self , refresh_token : str , audience : Optional [ Iterable [str ] | str ] = None
361+ self , refresh_token : str , audience : Iterable [str ] | str | None = None
355362 ) -> dict :
356363 """
357364 Refresh a session. Call this function when a session expires and needs to be refreshed.
@@ -372,7 +379,7 @@ def validate_and_refresh_session(
372379 self ,
373380 session_token : str ,
374381 refresh_token : str ,
375- audience : Optional [ Iterable [str ] | str ] = None ,
382+ audience : Iterable [str ] | str | None = None ,
376383 ) -> dict :
377384 """
378385 Validate the session token and refresh it if it has expired, the session token will automatically be refreshed.
@@ -472,7 +479,7 @@ def my_tenants(
472479 self ,
473480 refresh_token : str ,
474481 dct : bool = False ,
475- ids : Optional [ list [str ]] = None ,
482+ ids : list [str ] | None = None ,
476483 ) -> dict :
477484 """
478485 Retrieve tenant attributes that user belongs to, one of dct/ids must be populated .
@@ -553,8 +560,8 @@ def history(self, refresh_token: str) -> list[dict]:
553560 def exchange_access_key (
554561 self ,
555562 access_key : str ,
556- audience : Optional [ Iterable [str ] | str ] = None ,
557- login_options : Optional [ AccessKeyLoginOptions ] = None ,
563+ audience : Iterable [str ] | str | None = None ,
564+ login_options : AccessKeyLoginOptions | None = None ,
558565 ) -> dict :
559566 """
560567 Return a new session token for the given access key
@@ -595,3 +602,35 @@ def select_tenant(
595602 AuthException: Exception is raised if session is not authorized or another error occurs
596603 """
597604 return self ._auth .select_tenant (tenant_id , refresh_token )
605+
606+ def get_last_response (self ):
607+ """
608+ Get the last HTTP response from either auth or management operations.
609+
610+ Only available when verbose mode is enabled during client initialization.
611+ This provides access to HTTP metadata like headers (cf-ray), status codes,
612+ and raw response data for debugging failed requests.
613+
614+ Returns:
615+ DescopeResponse: The last response if verbose mode is enabled.
616+ Returns the most recent response from either auth or mgmt operations.
617+ None if verbose mode is disabled or no requests have been made.
618+
619+ Example:
620+ client = DescopeClient(project_id, management_key, verbose=True)
621+ try:
622+ client.mgmt.user.create(login_id="[email protected] ") 623+ except AuthException:
624+ resp = client.get_last_response()
625+ if resp:
626+ # Access metadata for debugging
627+ cf_ray = resp.headers.get("cf-ray")
628+ status = resp.status_code
629+ """
630+ # Return the most recently used response
631+ mgmt_resp = self ._mgmt_http_client .get_last_response ()
632+ auth_resp = self ._auth_http_client .get_last_response ()
633+
634+ # Return whichever is not None, preferring mgmt if both exist
635+ # (in practice, only one should be non-None at a time)
636+ return mgmt_resp or auth_resp
0 commit comments