Skip to content

fix: add friendly Gradata auth error#264

Open
Gradata wants to merge 1 commit into
mainfrom
gra-979-auth-error
Open

fix: add friendly Gradata auth error#264
Gradata wants to merge 1 commit into
mainfrom
gra-979-auth-error

Conversation

@Gradata

@Gradata Gradata commented Jun 7, 2026

Copy link
Copy Markdown
Owner

Summary

  • add GradataAuthError and export it from gradata
  • fail fast from Brain(...) with a friendly missing-key message when no explicit/env/keyfile API key is available
  • allow Brain.init(...) to keep local-first initialization working, and allow Brain(..., api_key=...)
  • wrap cloud 401/403 responses in the same friendly auth error

Verification

  • env -u BRAIN_DIR -u GRADATA_BRAIN python3 -m pytest tests/test_auth_error.py tests/test_brain_write_through.py -q
  • git diff --check

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough
  • New public API: GradataAuthError exception added and exported from gradata package for authentication-related failures
  • Breaking change: Brain() now fails fast with a friendly error message when authentication is required but no API key is provided (via environment, keyfile, or explicit parameter)
  • New parameter: Brain.__init__() accepts optional api_key parameter to allow explicit API key passing
  • Updated signature: _resolve_sync_api_key() now accepts optional api_key override parameter
  • Error handling: Cloud client wraps HTTP 401/403 responses in GradataAuthError with consistent messaging
  • Onboarding fix: onboard() now skips auth checks during initialization via _skip_auth_check=True
  • Consistent messaging: New AUTH_ERROR_MESSAGE module constant provides unified authentication error messages
  • Test coverage: New test_auth_error.py module with tests for auth error scenarios and explicit API key handling

Walkthrough

This PR introduces explicit Cloud API authentication error handling to the Brain class. A new GradataAuthError exception is defined and exported at package level. Brain.__init__ now accepts an optional api_key parameter and _skip_auth_check flag, conditionally raising GradataAuthError when authentication is required but unavailable. The cloud client maps HTTP 401/403 responses to this error type, and onboard() bypasses auth checks. Tests validate the new behavior.

Changes

Cloud API Authentication Enforcement

Layer / File(s) Summary
Exception Definition and Export
src/gradata/exceptions.py, src/gradata/__init__.py
New GradataAuthError exception class is defined under the BrainError hierarchy and re-exported from the gradata package public API.
Brain Authentication Parameters and Resolution
src/gradata/brain.py
Brain.__init__ gains api_key and _skip_auth_check parameters; _resolve_sync_api_key accepts optional api_key override; module-level AUTH_ERROR_MESSAGE constant introduced; conditional GradataAuthError raised when auth required but no key available; sync worker reuses already-resolved self._api_key.
Cloud Client Authentication Error Handling
src/gradata/cloud/client.py
Cloud client imports GradataAuthError and AUTH_ERROR_MESSAGE; HTTP 401/403 responses now raise GradataAuthError instead of generic ConnectionError.
Onboarding Authentication Bypass
src/gradata/onboard.py
onboard() constructs Brain with _skip_auth_check=True to permit initialization without API key validation.
Authentication Error Test Coverage
tests/test_auth_error.py
New test module validates Brain constructor raises GradataAuthError with exact message when GRADATA_API_KEY is missing; confirms explicit api_key parameter allows construction and sets brain._api_key.

🎯 3 (Moderate) | ⏱️ ~20 minutes

bug, feature

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a friendly Gradata authentication error to the codebase, which is the core objective of this pull request.
Description check ✅ Passed The description is directly related to the changeset, clearly outlining the key changes: introducing GradataAuthError, failing fast with a friendly message, allowing explicit API key passing, and wrapping cloud responses.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gra-979-auth-error

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 OpenGrep (1.22.0)

OpenGrep fatal error (exit code 2):
┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

�[1m Loading rules from local config...�[0m
[00.19][ERROR]: Error: exception Glob.Lexer.Syntax_error("malformed glob pattern: missing ']'")
Raised at Glob__Lexer.syntax_error in file "libs/glob/Lexer.mll", line 8, characters 2-26
Called from Glob__Lexer.__ocaml_lex_token_rec in file "libs/glob/Lexer.mll", line 29, characters 26-53
Cal


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added bug Something isn't working feature labels Jun 7, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Gradata/src/gradata/cloud/client.py`:
- Around line 339-343: Add a deterministic unit test that mocks/raises HTTPError
with .code set to 401 and 403 and asserts that the call raises GradataAuthError
(use the same client method that contains the except HTTPError block to trigger
the handler); create separate subtests for 401 and 403, and also include a
negative/other-code case (e.g., 413) to assert the mapping to _TooLargeError
remains unchanged so regressions are caught. Use the real HTTPError,
GradataAuthError, and _TooLargeError symbols from the client code and patch the
network call or method that would raise HTTPError to simulate the conditions.
Ensure the test is isolated, deterministic, and added to the test suite where
other cloud client tests live.
- Around line 27-28: AUTH_ERROR_MESSAGE currently imported in
Gradata/src/gradata/cloud/client.py from gradata.brain creates an unnecessary
coupling; move the constant into gradata.exceptions (or a small shared module)
and update imports. Specifically, add AUTH_ERROR_MESSAGE to gradata.exceptions
(next to GradataAuthError), change the import in cloud/client.py to from
gradata.exceptions import AUTH_ERROR_MESSAGE, GradataAuthError, and update any
other modules (e.g., gradata.brain) to import AUTH_ERROR_MESSAGE from
gradata.exceptions instead of defining or exporting it from gradata.brain.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b53dc92d-c8e7-473f-beb8-b6112823ea04

📥 Commits

Reviewing files that changed from the base of the PR and between e0fa6b0 and 1b4f2ea.

📒 Files selected for processing (6)
  • Gradata/src/gradata/__init__.py
  • Gradata/src/gradata/brain.py
  • Gradata/src/gradata/cloud/client.py
  • Gradata/src/gradata/exceptions.py
  • Gradata/src/gradata/onboard.py
  • Gradata/tests/test_auth_error.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: pytest macos-latest / py3.11
  • GitHub Check: pytest windows-latest / py3.11
  • GitHub Check: pytest macos-latest / py3.12
  • GitHub Check: pytest ubuntu-latest / py3.11
  • GitHub Check: pytest ubuntu-latest / py3.12
  • GitHub Check: pytest windows-latest / py3.12
  • GitHub Check: pytest (py3.11)
  • GitHub Check: pytest (py3.12)
🧰 Additional context used
📓 Path-based instructions (2)
Gradata/tests/**/*.py

📄 CodeRabbit inference engine (Gradata/AGENTS.md)

Gradata/tests/**/*.py: Set BRAIN_DIR environment variable via tmp_path in conftest.py for test isolation — ensure _paths.py module cache refreshes when calling Brain.init() directly inside tests
Add unit tests in tests/test_*.py for every CI push without LLM calls (deterministic); mark integration tests with @pytest.mark.integration and skip them by default (they hit real LLM APIs)

Files:

  • Gradata/tests/test_auth_error.py
Gradata/src/**/*.py

📄 CodeRabbit inference engine (Gradata/AGENTS.md)

Gradata/src/**/*.py: Prefer sentence-transformers for local embeddings, google-genai for Gemini embeddings, cryptography for AES-GCM encrypted system.db, bm25s for BM25 rule ranking, and mem0ai for external memory adapters — guard all optional dependency imports with try / except ImportError at the call site, never at module level
Maintain strict layering: Layer 0 (Primitives: _types.py, _db.py, _events.py, _paths.py, _file_lock.py; Patterns: contrib/patterns/) must never import from Layer 1 (Enhancements: enhancements/, rules/) or Layer 2 (Public API: brain.py, cli.py, daemon.py, mcp_server.py)
Never use bare except: pass — use typed exceptions or at minimum logger.warning(...) with exc_info=True to avoid silent failure in a memory product
Never import from out-of-scope sibling directories ../Sprites/ or ../Hausgem/ within gradata/* code — that is a layering bug
Never leak private-sibling paths into public docs/code — no references to ../Sprites/, ../Hausgem/, email addresses, OneDrive paths, or Sprites-specific examples from inside gradata/*
Use atomic-write helper when writing JSON files to prevent corruption from mid-write crashes

Files:

  • Gradata/src/gradata/__init__.py
  • Gradata/src/gradata/onboard.py
  • Gradata/src/gradata/cloud/client.py
  • Gradata/src/gradata/exceptions.py
  • Gradata/src/gradata/brain.py
🧠 Learnings (7)
📓 Common learnings
Learnt from: Gradata
Repo: Gradata/gradata PR: 0
File: :0-0
Timestamp: 2026-04-17T17:18:07.439Z
Learning: In PR `#102` (gradata/gradata), Round 2 addressed: cli.py env-first brain resolution (GRADATA_BRAIN > --brain-dir > cwd), _tenant.py corrupt .tenant_id overwrite, _env_int default clamping to minimum, and _events.py tenant-scoped fallback SELECT for dedup. All ruff and 99 tests green after these fixes.
📚 Learning: 2026-05-01T15:50:32.772Z
Learnt from: CR
Repo: Gradata/gradata PR: 0
File: Gradata/AGENTS.md:0-0
Timestamp: 2026-05-01T15:50:32.772Z
Learning: Applies to Gradata/tests/**/*.py : Set `BRAIN_DIR` environment variable via `tmp_path` in conftest.py for test isolation — ensure `_paths.py` module cache refreshes when calling `Brain.init()` directly inside tests

Applied to files:

  • Gradata/tests/test_auth_error.py
📚 Learning: 2026-04-17T17:18:07.439Z
Learnt from: Gradata
Repo: Gradata/gradata PR: 0
File: :0-0
Timestamp: 2026-04-17T17:18:07.439Z
Learning: In PR `#102` (gradata/gradata), Round 2 addressed: cli.py env-first brain resolution (GRADATA_BRAIN > --brain-dir > cwd), _tenant.py corrupt .tenant_id overwrite, _env_int default clamping to minimum, and _events.py tenant-scoped fallback SELECT for dedup. All ruff and 99 tests green after these fixes.

Applied to files:

  • Gradata/tests/test_auth_error.py
📚 Learning: 2026-05-01T15:50:32.772Z
Learnt from: CR
Repo: Gradata/gradata PR: 0
File: Gradata/AGENTS.md:0-0
Timestamp: 2026-05-01T15:50:32.772Z
Learning: Applies to Gradata/src/**/*.py : Maintain strict layering: Layer 0 (Primitives: _types.py, _db.py, _events.py, _paths.py, _file_lock.py; Patterns: contrib/patterns/*) must never import from Layer 1 (Enhancements: enhancements/*, rules/) or Layer 2 (Public API: brain.py, cli.py, daemon.py, mcp_server.py)

Applied to files:

  • Gradata/src/gradata/__init__.py
📚 Learning: 2026-05-01T15:50:32.772Z
Learnt from: CR
Repo: Gradata/gradata PR: 0
File: Gradata/AGENTS.md:0-0
Timestamp: 2026-05-01T15:50:32.772Z
Learning: Applies to Gradata/src/**/*.py : Prefer `sentence-transformers` for local embeddings, `google-genai` for Gemini embeddings, `cryptography` for AES-GCM encrypted system.db, `bm25s` for BM25 rule ranking, and `mem0ai` for external memory adapters — guard all optional dependency imports with `try / except ImportError` at the call site, never at module level

Applied to files:

  • Gradata/src/gradata/__init__.py
📚 Learning: 2026-05-01T15:50:32.772Z
Learnt from: CR
Repo: Gradata/gradata PR: 0
File: Gradata/AGENTS.md:0-0
Timestamp: 2026-05-01T15:50:32.772Z
Learning: Applies to Gradata/**/pyproject.toml : Maintain `dependencies = []` in pyproject.toml — the base package is pure Python + stdlib with all heavy dependencies gated as optional extras: embeddings, gemini, encrypted, ranking, adapters-mem0

Applied to files:

  • Gradata/src/gradata/__init__.py
📚 Learning: 2026-05-01T15:50:32.772Z
Learnt from: CR
Repo: Gradata/gradata PR: 0
File: Gradata/AGENTS.md:0-0
Timestamp: 2026-05-01T15:50:32.772Z
Learning: Use `from gradata import Brain` as the public entry point — `brain.correct()` is THE entry point for the headline product promise

Applied to files:

  • Gradata/src/gradata/onboard.py
  • Gradata/src/gradata/brain.py
🔇 Additional comments (5)
Gradata/src/gradata/exceptions.py (1)

25-27: LGTM!

Gradata/src/gradata/__init__.py (1)

65-65: LGTM!

Also applies to: 84-84

Gradata/src/gradata/brain.py (1)

57-60: LGTM!

Also applies to: 63-70, 74-75, 101-110, 298-298

Gradata/src/gradata/onboard.py (1)

490-490: LGTM!

Gradata/tests/test_auth_error.py (1)

9-35: LGTM!

Comment on lines +27 to +28
from gradata.brain import AUTH_ERROR_MESSAGE
from gradata.exceptions import GradataAuthError

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Decouple auth message constant from gradata.brain to reduce module coupling.

Importing AUTH_ERROR_MESSAGE from gradata.brain makes the cloud client depend on the full Brain module. Move the shared message constant to gradata.exceptions (or a tiny shared auth constants module) and import it from both callers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Gradata/src/gradata/cloud/client.py` around lines 27 - 28, AUTH_ERROR_MESSAGE
currently imported in Gradata/src/gradata/cloud/client.py from gradata.brain
creates an unnecessary coupling; move the constant into gradata.exceptions (or a
small shared module) and update imports. Specifically, add AUTH_ERROR_MESSAGE to
gradata.exceptions (next to GradataAuthError), change the import in
cloud/client.py to from gradata.exceptions import AUTH_ERROR_MESSAGE,
GradataAuthError, and update any other modules (e.g., gradata.brain) to import
AUTH_ERROR_MESSAGE from gradata.exceptions instead of defining or exporting it
from gradata.brain.

Comment on lines +339 to +343
except HTTPError as e:
if e.code in (401, 403):
raise GradataAuthError(AUTH_ERROR_MESSAGE) from e
if e.code == 413:
raise _TooLargeError() from e

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add a focused unit test for 401/403 → GradataAuthError mapping.

This is a behavior contract change; a deterministic test around mocked HTTPError for 401/403 will prevent regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Gradata/src/gradata/cloud/client.py` around lines 339 - 343, Add a
deterministic unit test that mocks/raises HTTPError with .code set to 401 and
403 and asserts that the call raises GradataAuthError (use the same client
method that contains the except HTTPError block to trigger the handler); create
separate subtests for 401 and 403, and also include a negative/other-code case
(e.g., 413) to assert the mapping to _TooLargeError remains unchanged so
regressions are caught. Use the real HTTPError, GradataAuthError, and
_TooLargeError symbols from the client code and patch the network call or method
that would raise HTTPError to simulate the conditions. Ensure the test is
isolated, deterministic, and added to the test suite where other cloud client
tests live.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant