Skip to content

fix(sqla): recognise flask-sqlalchemy-lite when its first import fails - #2998

Closed
vishalkaay wants to merge 1 commit into
pallets-eco:masterfrom
vishalkaay:fix/sqla-lite-broken-import
Closed

vishalkaay wants to merge 1 commit into
pallets-eco:masterfrom
vishalkaay:fix/sqla-lite-broken-import

Conversation

@vishalkaay

@vishalkaay vishalkaay commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Every SQLAlchemy list view fails with AttributeError: 'SQLAlchemy' object has no attribute 'query' when Flask-SQLAlchemy-Lite is used with SQLAlchemy 2.1 and greenlet is not installed (#2994).

Cause. SQLAlchemy 2.1 no longer installs greenlet by default. flask_sqlalchemy_lite imports sqlalchemy.ext.asyncio, which raises ImportError without it. That import fails only the first time in a process; a second attempt succeeds:

>>> from flask_sqlalchemy_lite import SQLAlchemy
ImportError: The SQLAlchemy asyncio module requires that the Python 'greenlet' library is installed. ...
>>> from flask_sqlalchemy_lite import SQLAlchemy
>>> SQLAlchemy
<class 'flask_sqlalchemy_lite._extension.SQLAlchemy'>

flask_admin/contrib/sqla/_types.py catches the ImportError and sets T_SQLALCHEMY_LITE = None. So when flask_admin is imported before the application imports flask_sqlalchemy_lite, Flask-Admin absorbs the one failure, the application's import works, and _is_db_extension(db) is false for the application's db object. get_query() then calls .query on the extension instead of its session. SQLAlchemy 2.0 is unaffected because it depends on greenlet.

Change. _is_db_extension() now falls back to the flask_sqlalchemy_lite module in sys.modules when Flask-Admin's own import failed. An application can only hold a Flask-SQLAlchemy-Lite db object if that module is loaded, so the object is recognised whichever import ran first. Nothing changes when the import in _types succeeds, or when the package cannot be imported at all (e.g. installed next to SQLAlchemy 1.4).

The first version of this PR re-raised the import error instead. That broke py311-min, where Flask-SQLAlchemy-Lite is installed alongside SQLAlchemy 1.4 and can never import, so I replaced it with this approach.

The new test simulates the failed import and checks that a view created with a Flask-SQLAlchemy-Lite db object still serves its list view. It fails on master. I also checked the real case by hand: SQLAlchemy 2.1.1 without greenlet now returns 200 for the list view.

fixes #2994

SQLAlchemy 2.1 no longer installs greenlet by default. Without it,
importing Flask-SQLAlchemy-Lite fails the first time in a process and
succeeds the second time. Flask-Admin's optional import absorbed that
failure and treated the package as not installed, the application's
own import then worked, and its db object was no longer recognised:
every list view failed with
`AttributeError: 'SQLAlchemy' object has no attribute 'query'`.

Fall back to the module the application loaded when checking for the
extension class, so the db object is recognised whichever import ran
first.
@vishalkaay
vishalkaay force-pushed the fix/sqla-lite-broken-import branch from 6d7d9d2 to 25f3126 Compare November 10, 2026 20:07
@vishalkaay vishalkaay changed the title fix(sqla): don't hide a failing flask-sqlalchemy-lite import fix(sqla): recognise flask-sqlalchemy-lite when its first import fails Nov 10, 2026
@ElLorans

Copy link
Copy Markdown
Collaborator

Isn't this a bug in flask-sqlalchemy-lite or even asyncio/(greenlet more than in flask-admin?
Why do we need additional test case? Upgrading the lockfile is sufficient

@ElLorans

Copy link
Copy Markdown
Collaborator

flask-sqla-lite even declares "sqlalchemy[asyncio]>=2.0.31", as a dependency, so greenlet should be installed when flask-sqlalite is present

@ElLorans

Copy link
Copy Markdown
Collaborator

even uv tree --script a.py marks greenlet as required dependency of flask-sqla-lite

Resolved 14 packages in 12ms
sqlalchemy v2.1.8
└── typing-extensions v4.16.0
flask-sqlalchemy-lite v0.2.1
├── flask[async] v3.1.3
│   ├── blinker v1.9.0
│   ├── click v8.5.1
│   ├── itsdangerous v2.2.0
│   ├── jinja2 v3.1.6
│   │   └── markupsafe v3.0.4
│   ├── markupsafe v3.0.4
│   ├── werkzeug v3.1.9
│   │   └── markupsafe v3.0.4
│   └── asgiref v3.13.0 (extra: async)
└── sqlalchemy[asyncio] v2.1.8
    ├── typing-extensions v4.16.0
    └── greenlet v3.5.6 (extra: asyncio)
flask-admin v2.2.1
├── flask v3.1.3
│   ├── blinker v1.9.0
│   ├── click v8.5.1
│   ├── itsdangerous v2.2.0
│   ├── jinja2 v3.1.6 (*)
│   ├── markupsafe v3.0.4
│   └── werkzeug v3.1.9 (*)
├── jinja2 v3.1.6 (*)
├── markupsafe v3.0.4
├── werkzeug v3.1.9 (*)
└── wtforms v3.3.2
    └── markupsafe v3.0.4

@ElLorans ElLorans closed this Nov 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

sqlalchemy-2.1: AttributeError: 'SQLAlchemy' object has no attribute 'query'

2 participants