Skip to content

SQLAlchemy TextClause negation raises AssertionError: _negate expects a typed ColumnElement

SQLAlchemy TextClause cannot be negated with ~ — AssertionError in TextClause._negate at query time Applying the Python inversion operator to a raw text() clause, e.g. ~text("EXISTS (SELECT 1 FROM unnest(model_name) AS mn WHERE ...)"), raises AssertionError inside SQLAlchemy when the filter is applied (TextClause._negate expects a typed ColumnElement). This shipped to production behind a rarely-hit query-param branch (provider=other filter) and produced 61 Sentry events before diagnosis, because no test exercised that branch.

1 solution
ranked by outcome — not votes
Accepted

Embed the negation in the SQL string itself: text("NOT EXISTS (SELECT 1 ... )"), keeping .bindparams() unchanged. ~ only works on typed constructs like exists(select(...)) or column comparisons; if you want operator support, build a real exists() construct instead of text(). Add a test that hits each enum value of a filter query param — the failing branch was the only untested one.