Fix: raise an actionable ImportError when sqlalchemy is missing, instead of a guard that does nothing - #167
Open
AmaadMartin wants to merge 2 commits into
Open
Fix: raise an actionable ImportError when sqlalchemy is missing, instead of a guard that does nothing#167AmaadMartin wants to merge 2 commits into
AmaadMartin wants to merge 2 commits into
Conversation
added 2 commits
August 7, 2026 17:47
…bsent
`database_session_service` wrapped its sqlalchemy imports in
`except ImportError: pass`, but the schema and migration modules it imports
below need sqlalchemy too. The guard bought nothing: the module has never been
importable without the `db` extra. It only hid the requirement, so
`import google.adk.sessions.database_session_service` and the CLI database and
migration paths reported a bare `No module named 'sqlalchemy'`.
Replace the swallow with `raise missing_extra("sqlalchemy", "db") from e`, the
message `sessions/__init__.py` already produces for this module. Apply the same
change to `migration/_schema_check_utils.py`, which today leaves itself
half-initialised and fails later with a `NameError`.
`sqlalchemy` stays an optional extra. With it installed, behaviour is unchanged.
…ise made dead `DatabaseSessionService.__init__` re-checked `import sqlalchemy`, and `sessions/__init__.py` special-cased `DatabaseSessionService` to wrap an ImportError from the module. The module now raises at import time, so neither can run with sqlalchemy absent. The `__init__.py` branch also relabelled any unrelated ImportError from the module as a missing `db` extra. `DatabaseSessionService` joins `_LAZY_MEMBERS` instead, so the accessor reports what actually failed. The missing-sqlalchemy message is unchanged, and its `__cause__` is now the original ModuleNotFoundError rather than a second copy of itself.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
N/A
Problem:
sessions/database_session_service.pywraps its sqlalchemy imports inexcept ImportError: pass, but the schema and migration modules it imports below need sqlalchemy too. The guard buys nothing: the module has never been importable without thedbextra. It only hides the requirement, soimport google.adk.sessions.database_session_serviceand both CLI database paths report a bareNo module named 'sqlalchemy'.sessions/migration/_schema_check_utils.pyhas the same guard and leaves itself half-loaded, so its functions die later withNameError: name 'create_sync_engine' is not defined.Solution: Both guards now raise
missing_extra("sqlalchemy", "db")from the original error. That is the messagesessions/__init__.pyalready produces for this module, so every entry point converges on one line:pip install google-adk[db].sqlalchemystays an optional extra; with it installed theexceptbranch never runs and behaviour is unchanged.The raise makes two older guards dead, so the second commit removes them.
DatabaseSessionService.__init__re-checkedimport sqlalchemy, which the module import now settles.sessions/__init__.pyspecial-casedDatabaseSessionServiceto wrap an ImportError from the module, which both duplicated the exception chain and relabelled any unrelated ImportError as a missingdbextra;DatabaseSessionServicejoins_LAZY_MEMBERSinstead.Collision check: I listed the 100 open PRs on the fork and read the changed-file list of each. Only #151 touches a file I touch (
tests/unittests/test_optional_dependencies.py), in a different section, and it does not change either production file. No PR overlaps this change.Testing Plan
Please describe the tests that you ran to verify your changes. This is required for all PRs that are not small documentation or typo fixes.
Unit Tests:
[x] I have added or updated unit tests for my change.
[x] All unit tests pass locally.
Two new tests cover the two new
exceptbodies, which is every new line and both directions of both branches. The sqlalchemy-present direction is covered by all 394 tests above. The second commit only deletes code, so it adds no new lines to cover.Mutation check. I restored both
except ImportError: passguards frommainand re-ran the file. Both new tests fail:test_database_session_service_import_reports_missing_extra:Failed: DID NOT RAISE ImportError. Run on its own it fails differently, withRegex pattern did not match. Actual message: 'import of sqlalchemy halted; None in sys.modules'. That split is the bug: once an earlier test has imported the schema modules, the old guard swallows the sqlalchemy failure and the module imports clean.test_schema_check_utils_import_reports_missing_extra:Failed: DID NOT RAISE ImportErrorThe other five tests in the file still pass on the restored guards. That includes
test_database_session_service_fails_on_creation, which asserts only"sqlalchemy" in str(exc)."sqlalchemy"is in the broken message too, so that test cannot see this bug. The new tests matchgoogle-adk[db]for that reason.Manual End-to-End (E2E) Tests:
Build a core-only environment and check each entry point.
All four print
ImportError: The 'sqlalchemy' package is required to use this feature. Please install it by running: pip install google-adk[db]. Before this change the first three printedModuleNotFoundError: No module named 'sqlalchemy'and the fourth printedNameError: name 'create_sync_engine' is not defined.adk migrate sessionin the same environment printsMigration failed: The 'sqlalchemy' package is required to use this feature. Please install it by running: pip install google-adk[db].Core install is unaffected:
import google.adkandInMemorySessionServicestill work, andgoogle.adk.sessions.Nopestill raisesAttributeError.The accessor chain is now one wrap deep, not two:
ImportError: ... pip install google-adk[db]with__cause__set toModuleNotFoundError: No module named 'sqlalchemy'.Other checks on the pushed commit:
isort --check-only,pyink --checkandmypyon the changed files. mypy reports the same single pre-existing error before and after, so this adds no new error to the CI baseline.CI on the first commit: Unit Tests and Mypy Check passed on Python 3.10 through 3.14, and so did the A2A v0.3 tests. Pre-commit Linter fails, for a reason unrelated to this change: the
update-constraintshook regeneratesconstraints-3.*.txtfrom live PyPI and rewrites them on every run. That hook fails the same way on PRs that touch neitherpyproject.tomlnor the constraints files, such as #165, #164 and #162, and PRs #163 and #110 already propose fixes for it. This change touches no dependency file, andpre-commit run --fileson the four changed files passes every hook.Checklist
[x] I have read the CONTRIBUTING.md document.
[x] I have performed a self-review of my own code.
[x] I have commented my code, particularly in hard-to-understand areas.
[x] I have added tests that prove my fix is effective or that my feature works.
[x] New and existing unit tests pass locally with my changes.