<h3 dir="auto">Ensure stubs packages are not installed</h3>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> No sqlalchemy stub packages is installed (both <code class="notranslate">sqlalchemy-stubs</code> and <code class="notranslate">sqlalchemy2-stubs</code> are not compatible with v2)</li>
</ul>
<h3 dir="auto">Verify if the api is typed</h3>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> The api is not in a module listed in <a href="https://github.com/sqlalchemy/sqlalchemy/issues/6810" data-hovercard-type="issue" data-hovercard-url="/sqlalchemy/sqlalchemy/issues/6810/hovercard">#6810</a> so it should pass type checking</li>
</ul>
<h3 dir="auto">Describe the typing issue</h3>
<p dir="auto">Hello,</p>
<p dir="auto">I was wondering if you would be open to converting <code class="notranslate">_ColumnExpressionArgument</code> from a private, underscore prefixed type to <code class="notranslate">ColumnExpressionArgument</code>, a public and exported type.</p>
<p dir="auto">This would allow users who wrap SQLAlchemy expressions but accept where clauses from the caller to represent the where clause in the same way the underlying where call does, as seen and defined in these links:</p>
<ul dir="auto">
<li><code class="notranslate">where</code>: <div class="Box Box--condensed my-2">
  <div class="Box-header f6">
    <p class="mb-0 text-bold">
      <a href="https://github.com/sqlalchemy/sqlalchemy/blob/6f3c741e41e13624d63f4faa4bdcec8466bda7f4/lib/sqlalchemy/orm/query.py#L1868">sqlalchemy/lib/sqlalchemy/orm/query.py</a>
    </p>
    <p class="mb-0 color-fg-muted">
         Line 1868
      in
      <a data-pjax="true" class="commit-tease-sha" href="/sqlalchemy/sqlalchemy/commit/6f3c741e41e13624d63f4faa4bdcec8466bda7f4">6f3c741</a>
    </p>
  </div>
  <div itemprop="text" class="Box-body p-0 blob-wrapper blob-wrapper-embedded data">
    <table class="highlight tab-size mb-0 js-file-line-container" data-tab-size="8" data-paste-markdown-skip="">

        <tbody><tr class="border-0">
          <td id="L1868" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="1868"></td>
          <td id="LC1868" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> <span class="pl-k">def</span> <span class="pl-en">where</span>(<span class="pl-s1">self</span>, <span class="pl-c1">*</span><span class="pl-s1">criterion</span>: <span class="pl-s1">_ColumnExpressionArgument</span>[<span class="pl-s1">bool</span>]) <span class="pl-c1">-&gt;</span> <span class="pl-v">Self</span>: </td>
        </tr>
    </tbody></table>
  </div>
</div>
</li>
<li><code class="notranslate">ColumnExpressionArgument</code>: <div class="Box Box--condensed my-2">
  <div class="Box-header f6">
    <p class="mb-0 text-bold">
      <a href="https://github.com/sqlalchemy/sqlalchemy/blob/6f3c741e41e13624d63f4faa4bdcec8466bda7f4/lib/sqlalchemy/sql/_typing.py#L169">sqlalchemy/lib/sqlalchemy/sql/_typing.py</a>
    </p>
    <p class="mb-0 color-fg-muted">
         Line 169
      in
      <a data-pjax="true" class="commit-tease-sha" href="/sqlalchemy/sqlalchemy/commit/6f3c741e41e13624d63f4faa4bdcec8466bda7f4">6f3c741</a>
    </p>
  </div>
  <div itemprop="text" class="Box-body p-0 blob-wrapper blob-wrapper-embedded data">
    <table class="highlight tab-size mb-0 js-file-line-container" data-tab-size="8" data-paste-markdown-skip="">

        <tbody><tr class="border-0">
          <td id="L169" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="169"></td>
          <td id="LC169" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> <span class="pl-s1">_ColumnExpressionArgument</span> <span class="pl-c1">=</span> <span class="pl-v">Union</span>[ </td>
        </tr>
    </tbody></table>
  </div>
</div>
</li>
</ul>
<h3 dir="auto">To Reproduce</h3>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="Simple use case:


def list_where(session, *criterion: _ColumnExpressionArgument[bool]) -&gt; Sequence[Model]:
    return session.execute(select(Model).where(*criterion)).scalars().all()


This is obviously contrived, while the more realistic use case is when an application requires consistent use of joins and wants to wrap the query to reduce duplication."><pre class="notranslate"><span class="pl-v">Simple</span> <span class="pl-s1">use</span> <span class="pl-k">case</span>:


<span class="pl-s1">def</span> <span class="pl-en">list_where</span>(<span class="pl-s1">session</span>, <span class="pl-c1">*</span><span class="pl-s1">criterion</span>: <span class="pl-s1">_ColumnExpressionArgument</span>[<span class="pl-s1">bool</span>]) <span class="pl-c1">-</span><span class="pl-c1">&gt;</span> <span class="pl-v">Sequence</span>[<span class="pl-v">Model</span>]:
    <span class="pl-s1">return</span> <span class="pl-s1">session</span>.<span class="pl-en">execute</span>(<span class="pl-en">select</span>(<span class="pl-v">Model</span>).<span class="pl-en">where</span>(<span class="pl-c1">*</span><span class="pl-s1">criterion</span>)).<span class="pl-en">scalars</span>().<span class="pl-en">all</span>()


<span class="pl-v">This</span> <span class="pl-c1">is</span> <span class="pl-s1">obviously</span> <span class="pl-s1">contrived</span>, <span class="pl-s1">while</span> <span class="pl-s1">the</span> <span class="pl-s1">more</span> <span class="pl-s1">realistic</span> <span class="pl-s1">use</span> <span class="pl-k">case</span> <span class="pl-c1">is</span> <span class="pl-s1">when</span> <span class="pl-s1">an</span> <span class="pl-s1">application</span> <span class="pl-s1">requires</span> <span class="pl-s1">consistent</span> <span class="pl-s1">use</span> <span class="pl-s1">of</span> <span class="pl-s1">joins</span> <span class="pl-c1">and</span> <span class="pl-s1">wants</span> <span class="pl-s1">to</span> <span class="pl-s1">wrap</span> <span class="pl-s1">the</span> <span class="pl-s1">query</span> <span class="pl-s1">to</span> <span class="pl-s1">reduce</span> <span class="pl-s1">duplication</span>.</pre></div>
<h3 dir="auto">Error</h3>
<p dir="auto">N/A</p>
<h3 dir="auto">Versions</h3>
<p dir="auto">N/A</p>
<h3 dir="auto">Additional context</h3>
<p dir="auto">Thank you for your time and consideration</p>
<p dir="auto"><strong>Migrated issue, originally created by Denis Otkidach (<a href="https://github.com/ods">@ods</a>)</strong></p>
<p dir="auto">According to documentation after_attach event should be generated for <code class="notranslate">add()</code>, <code class="notranslate">merge()</code>, and <code class="notranslate">delete()</code> calls. But the last doesn't work. Here is a code sample to reproduce the problem:</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, create_engine, event
from sqlalchemy.orm import sessionmaker, Session

Base = declarative_base()

class Obj(Base):
    __tablename__ = 'objs'
    id = Column(Integer, primary_key=True)

engine = create_engine('sqlite://', echo=True)
Base.metadata.create_all(engine)
session = sessionmaker(bind=engine)()

class EventHandlerIsCalled(Exception): pass

obj = Obj()

session.add(obj)
session.commit()

@event.listens_for(Session, 'after_attach')
def after_attach(session, instance):
    raise EventHandlerIsCalled()

try:
    session.delete(obj)
except EventHandlerIsCalled:
    pass
else:
    assert False, 'after_attach event is not generated'"><pre class="notranslate"><code class="notranslate">from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, create_engine, event
from sqlalchemy.orm import sessionmaker, Session

Base = declarative_base()

class Obj(Base):
    __tablename__ = 'objs'
    id = Column(Integer, primary_key=True)

engine = create_engine('sqlite://', echo=True)
Base.metadata.create_all(engine)
session = sessionmaker(bind=engine)()

class EventHandlerIsCalled(Exception): pass

obj = Obj()

session.add(obj)
session.commit()

@event.listens_for(Session, 'after_attach')
def after_attach(session, instance):
    raise EventHandlerIsCalled()

try:
    session.delete(obj)
except EventHandlerIsCalled:
    pass
else:
    assert False, 'after_attach event is not generated'
</code></pre></div>