@@ -98,6 +98,7 @@ class ClientKind(enum.Enum):
9898
9999 CLI = "cli"
100100 AUTH = "auth"
101+ NO_AUTH = "no_auth"
101102
102103
103104def add_correlation_id (request : httpx .Request ):
@@ -253,6 +254,8 @@ def load(self) -> Credentials:
253254 with open (config_path ) as f :
254255 credentials = json .load (f )
255256 self .api_url = credentials ["api_url" ]
257+ if self .client_kind == ClientKind .NO_AUTH :
258+ return self
256259 if self .api_token is not None :
257260 return self
258261 if os .getenv ("AIRFLOW_CLI_DEBUG_MODE" ) == "true" :
@@ -294,16 +297,17 @@ def load(self) -> Credentials:
294297 raise AirflowCtlKeyringException ("Keyring backend is not available" ) from e
295298 self .api_token = None
296299 except FileNotFoundError :
297- # This is expected during the auth login command
298- if self .client_kind != ClientKind .AUTH :
300+ # This is expected during the auth login command.
301+ # Also allow token-only usage without local config (for commands like `version --remote`).
302+ if self .client_kind not in (ClientKind .AUTH , ClientKind .NO_AUTH ) and self .api_token is None :
299303 raise AirflowCtlCredentialNotFoundException ("No credentials file found. Please login first." )
300304
301305 return self
302306
303307
304308class BearerAuth (httpx .Auth ):
305- def __init__ (self , token : str ):
306- self .token : str = token
309+ def __init__ (self , token : str | None ):
310+ self .token : str | None = token
307311
308312 def auth_flow (self , request : httpx .Request ):
309313 if self .token :
@@ -332,11 +336,13 @@ def __init__(
332336 self ,
333337 * ,
334338 base_url : str ,
335- token : str ,
336- kind : Literal [ClientKind .CLI , ClientKind .AUTH ] = ClientKind .CLI ,
339+ token : str | None = None ,
340+ kind : Literal [ClientKind .CLI , ClientKind .AUTH , ClientKind . NO_AUTH ] = ClientKind .CLI ,
337341 ** kwargs : Any ,
338342 ) -> None :
339- auth = BearerAuth (token )
343+ auth : httpx .Auth | None = None
344+ if kind != ClientKind .NO_AUTH :
345+ auth = BearerAuth (token )
340346 kwargs ["base_url" ] = self ._get_base_url (base_url = base_url , kind = kind )
341347 pyver = f"{ '.' .join (map (str , sys .version_info [:3 ]))} "
342348 super ().__init__ (
@@ -347,14 +353,18 @@ def __init__(
347353 )
348354
349355 def refresh_base_url (
350- self , base_url : str , kind : Literal [ClientKind .AUTH , ClientKind .CLI ] = ClientKind .CLI
356+ self ,
357+ base_url : str ,
358+ kind : Literal [ClientKind .AUTH , ClientKind .CLI , ClientKind .NO_AUTH ] = ClientKind .CLI ,
351359 ):
352360 """Refresh the base URL of the client."""
353361 self .base_url = URL (self ._get_base_url (base_url = base_url , kind = kind ))
354362
355363 @classmethod
356364 def _get_base_url (
357- cls , base_url : str , kind : Literal [ClientKind .AUTH , ClientKind .CLI ] = ClientKind .CLI
365+ cls ,
366+ base_url : str ,
367+ kind : Literal [ClientKind .AUTH , ClientKind .CLI , ClientKind .NO_AUTH ] = ClientKind .CLI ,
358368 ) -> str :
359369 """Get the base URL of the client."""
360370 base_url = base_url .rstrip ("/" )
@@ -466,7 +476,10 @@ def plugins(self):
466476
467477# API Client Decorator for CLI Actions
468478@contextlib .contextmanager
469- def get_client (kind : Literal [ClientKind .CLI , ClientKind .AUTH ] = ClientKind .CLI , api_token : str | None = None ):
479+ def get_client (
480+ kind : Literal [ClientKind .CLI , ClientKind .AUTH , ClientKind .NO_AUTH ] = ClientKind .CLI ,
481+ api_token : str | None = None ,
482+ ):
470483 """
471484 Get CLI API client.
472485
@@ -476,11 +489,16 @@ def get_client(kind: Literal[ClientKind.CLI, ClientKind.AUTH] = ClientKind.CLI,
476489 api_token = api_token or os .getenv ("AIRFLOW_CLI_TOKEN" , None )
477490 try :
478491 # API URL always loaded from the config file, please save with it if you are using other than ClientKind.CLI
479- credentials = Credentials (client_kind = kind , api_token = api_token ).load ()
492+ if kind == ClientKind .NO_AUTH :
493+ credentials = Credentials (client_kind = kind ).load ()
494+ resolved_token = None
495+ else :
496+ credentials = Credentials (client_kind = kind , api_token = api_token ).load ()
497+ resolved_token = api_token or credentials .api_token
480498 api_client = Client (
481499 base_url = credentials .api_url or "http://localhost:8080" ,
482500 limits = httpx .Limits (max_keepalive_connections = 1 , max_connections = 1 ),
483- token = str ( api_token or credentials . api_token ) ,
501+ token = resolved_token ,
484502 kind = kind ,
485503 )
486504 yield api_client
@@ -492,7 +510,7 @@ def get_client(kind: Literal[ClientKind.CLI, ClientKind.AUTH] = ClientKind.CLI,
492510
493511
494512def provide_api_client (
495- kind : Literal [ClientKind .CLI , ClientKind .AUTH ] = ClientKind .CLI ,
513+ kind : Literal [ClientKind .CLI , ClientKind .AUTH , ClientKind . NO_AUTH ] = ClientKind .CLI ,
496514) -> Callable [[Callable [PS , RT ]], Callable [PS , RT ]]:
497515 """
498516 Provide a CLI API Client to the decorated function.
0 commit comments