Repository: apache/airflow
File: airflow-ctl/src/airflowctl/api/client.py (Python)

Code before:
                        debug_credentials = json.load(df)
                        self.api_token = debug_credentials.get(f"api_token_{self.api_environment}")
                else:
                    try:
                        self.api_token = keyring.get_password(
                            "airflowctl", f"api_token_{self.api_environment}"
                        )
                    except ValueError as e:
                        # Incorrect keyring password
                        if self.client_kind == ClientKind.AUTH:
                            # For AUTH kind, log warning and continue (user is logging in)
                            log.warning(
                                "Could not access keyring for environment %s: %s", self.api_environment, e
                            )
                            self.api_token = None
                        else:
                            # For CLI kind, provide helpful error and raise
                            error_msg = (
                                f"Failed to access system keyring for environment '{self.api_environment}'. "
                                f"This usually means an incorrect keyring password was entered.\n"
                                f"To fix this:\n"
                                f"  1. Ensure your system keyring password is correct\n"
                                f"  2. Or use AIRFLOW_CLI_DEBUG_MODE=true to bypass keyring\n"
                                f"  3. Or run 'airflowctl auth login' to re-authenticate"
                            )
                            raise AirflowCtlCredentialNotFoundException(error_msg) from e
                    except NoKeyringError as e:
                        # No keyring backend available
                        log.error("No keyring backend available: %s", e)
                        if self.client_kind == ClientKind.CLI:
                            error_msg = (
                                "No keyring backend available to retrieve credentials.\n"
                                "Use AIRFLOW_CLI_DEBUG_MODE=true environment variable to store credentials in files instead."
                            )
                            raise AirflowCtlCredentialNotFoundException(error_msg) from e
                        self.api_token = None
        except FileNotFoundError:
            if self.client_kind == ClientKind.AUTH:
                # Saving the URL set from the Auth Commands if Kind is AUTH
                self.save()
            elif self.client_kind == ClientKind.CLI:
                raise AirflowCtlCredentialNotFoundException(
                    f"No credentials found in {default_config_dir} for environment {self.api_environment}."
                )
            else:
                raise AirflowCtlException(f"Unknown client kind: {self.client_kind}")

        return self


class BearerAuth(httpx.Auth):

Review comment (bug):
Looks good but one nit.

Should we just print the erorr aand `sys.exit(42)` here ?  cc: @bugraoz93 

I see that we are already handling it elsewhere:

```
    try:
        function(args)
    except (
        AirflowCtlCredentialNotFoundException,
        AirflowCtlConnectionException,
        AirflowCtlNotFoundException,
    ) as e:
        rich.print(f"command failed due to {e}")
        sys.exit(1)
```


I think in case of a fatal error in CLIs, I find the default exception handling as verbose and unnecessary - so my usual approach for fatal CLI errors is to print as descriptive and actionable error message as possible, with possibly even more details in debug mode, and .... exit with error. 

Otherwise - at least by default - your helpful error message will be overwhelmed by the stack trace printed by default - which usually in CLIs is very misleading, unhelpful for the users, confusing and intimidating.

So I would generally handle that in one of the two ways:

1) print error message + sys.exit()
2) raise a deliberate "ExitKindOfException" with appropriate error message, and handle it in one place with printing - but only printing THE MESSAGE - not the exception itself. 

I found such `sys.exit()` a bit better  because you can decide how to print he message, you can format and colour it apropriately ("actual error in red, warning - i.e. somtething to check -  in yellow, instructions to follow in white"),  this is pretty much approach we folow in breeze and it works nicely even for technical users of breeze, who could be - in principle - interested in stack trace and details, but  in fact they really want to understand what error it is and how they can fix it.

Approach 2 is also good (you can have some implicit dependency on rich and format your message when you prepare it with rich directives)  - also you can use some conventions to handle it differently in case of verbose output.

WDYT @bugraoz93 ? 

Code after:
                    with open(debug_creds_path) as df:
                        debug_credentials = json.load(df)
                        self.api_token = debug_credentials.get(f"api_token_{self.api_environment}")
                else:
                    try:
                        self.api_token = keyring.get_password(
                            "airflowctl", f"api_token_{self.api_environment}"
                        )
                    except ValueError as e:
                        # Incorrect keyring password
                        log.warning(
                            "Could not access keyring for environment %s: %s", self.api_environment, e
                        )
                        if self.client_kind == ClientKind.CLI:
                            raise AirflowCtlKeyringException(
                                f"Incorrect keyring password for environment {self.api_environment}"
                            ) from e
                        self.api_token = None
                    except NoKeyringError as e:
                        # No keyring backend available
                        log.error("No keyring backend available: %s", e)
                        if self.client_kind == ClientKind.CLI:
                            raise AirflowCtlKeyringException("Keyring backend is not available") from e
                        self.api_token = None
        except FileNotFoundError:
            if self.client_kind == ClientKind.AUTH:
                # Saving the URL set from the Auth Commands if Kind is AUTH
                self.save()
            elif self.client_kind == ClientKind.CLI:
                raise AirflowCtlCredentialNotFoundException("No credentials file found. Please login first.")
            else:
                raise AirflowCtlException(f"Unknown client kind: {self.client_kind}")

        return self


class BearerAuth(httpx.Auth):
    def __init__(self, token: str):
        self.token: str = token

    def auth_flow(self, request: httpx.Request):
        if self.token:
            request.headers["Authorization"] = "Bearer " + self.token
        yield request


class Client(httpx.Client):
    """Client for the Airflow REST API."""

    def __init__(
        self,