Conversation
Introduce a LangfuseError base class in langfuse/errors.py and derive all hand-written SDK exceptions from it: APIError, APIErrors, and RegressionError. Replace the last bare raise Exception in auth_check() with a new AuthError subclass. Export the hierarchy from the package root so applications can catch SDK errors precisely with 'except LangfuseError' while existing 'except Exception' handlers keep working unchanged. Closes langfuse#906
| raise AuthError( | ||
| "Auth check failed, no project found for the keys provided." | ||
| ) |
There was a problem hiding this comment.
Invalid credentials bypass AuthError When credentials are invalid,
projects.get() raises the generated UnauthorizedError before this new AuthError branch runs. UnauthorizedError is not a LangfuseError, and the existing handler catches a different generated error class. Callers using the new hierarchy therefore cannot catch this authentication failure. Handle the 401 response as AuthError at this boundary.
Knowledge Base Used: API client and service surface
Prompt To Fix With AI
This is a comment left during a code review.
Path: langfuse/_client/client.py
Line: 3512-3514
Comment:
**Invalid credentials bypass AuthError** When credentials are invalid, `projects.get()` raises the generated `UnauthorizedError` before this new `AuthError` branch runs. `UnauthorizedError` is not a `LangfuseError`, and the existing handler catches a different generated error class. Callers using the new hierarchy therefore cannot catch this authentication failure. Handle the 401 response as `AuthError` at this boundary.
**Knowledge Base Used:** [API client and service surface](https://app.greptile.com/personal-org-4986/-/custom-context/knowledge-base/langfuse/langfuse-python/-/docs/api-client-and-service-surface.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.There was a problem hiding this comment.
Good catch — fixed in a57ebff, and it was subtler than the diff suggested: the generated Error class the handler caught is itself just the generic 400 subclass of the fern ApiError base, so UnauthorizedError (a sibling subclass) escaped the handler entirely and reached callers raw, skipping even the handle_fern_exception logging.
auth_check() now catches ApiError instead, raises a chained AuthError for UnauthorizedError, and re-raises everything else unchanged (so non-auth fern errors keep their exact types). Added tests for the 401 → AuthError mapping and for a NotFoundError propagating unchanged.
Note: the same except Error pattern exists at a few other call sites in client.py (score/trace-tag helpers). I left those untouched to keep this PR scoped to the auth boundary — happy to do a separate pass converting them if that's wanted.
Invalid credentials never reached the AuthError branch: api.projects.get() raises the generated UnauthorizedError, which is not a subclass of the generated Error class that auth_check() caught, so it escaped the handler entirely. Catch the fern ApiError base instead, raise AuthError (chained) for UnauthorizedError, and re-raise everything else unchanged. Widen handle_fern_exception/generate_error_message_fern annotations to ApiError to match what they already handle at runtime.
Summary
Closes #906 (originally filed against the old SDK; rescoped to the current codebase after mapping the raise sites).
Since the original issue, the SDK has gained typed error classes in hand-written code (
APIError,APIErrorsinlangfuse/_utils/request.py,RegressionErrorinlangfuse/experiment.py), but they share no common base, only one of them was importable from the package root, and one bareraise Exceptionremained inauth_check(). This PR makes the SDK's errors precisely catchable:langfuse/errors.pydefiningLangfuseError(Exception)andAuthError(LangfuseError).APIError,APIErrors, andRegressionErrornow derive fromLangfuseError— purely additive, every class remains anException, so existingexcept Exceptionhandlers keep working.auth_check()raisesAuthErrorinstead of a bareExceptionwhen the credentials resolve to no project (docstring updated).LangfuseError,AuthError,APIError,APIErrorsare exported from the package root and listed in__all__.Errors raised by the generated API client (
langfuse.api) are intentionally left untouched (generated code); this is documented in the new module's docstring.Testing
tests/unit/test_errors.py: hierarchy/inheritance assertions,str()output unchanged forAPIError/APIErrors, andauth_check()behavior (raisesAuthErrorcatchable asLangfuseErroron empty project list, returnsTrueotherwise) via a mockedapi.projects.get.uv run --frozen pytest -n auto --dist worksteal tests/unit: 685 passed with this change vs 679 on the baseline (the +6 are the new tests); the same 3 pre-existing Windows-environment failures and 18 pre-existing setup errors occur with and without this change.uv run --frozen ruff check .,ruff format(changed files), anduv run --frozen mypy langfuse --no-error-summaryall clean.The PR is not ready to merge while invalid credentials bypass the new authentication error hierarchy.
Summary
The PR adds a hand-written SDK exception hierarchy, makes existing exceptions inherit from it, exports several error classes, and changes the empty-project
auth_check()failure toAuthError.APIErrorexport needs a clearer distinction from generated public API errors.Diagram
%%{init: {'theme': 'neutral'}}%% flowchart TD A[auth_check] --> B[Generated projects.get] B -->|Projects returned, empty| C[AuthError] B -->|HTTP 401| D[Generated UnauthorizedError] C --> E[LangfuseError] D --> F[Generated ApiError]Reviews (1) · Last reviewed commit: "feat: add LangfuseError exception hierar..."