<h3 dir="auto">Describe the use case</h3>
<p dir="auto">As mentioned in <a href="https://github.com/sqlalchemy/sqlalchemy/discussions/6199#discussioncomment-2006533" data-hovercard-type="discussion" data-hovercard-url="/sqlalchemy/sqlalchemy/discussions/6199/hovercard?comment_id=2006533">this discussion</a>, when we catch <code class="notranslate">sqlalchemy.exc.IntegrityError</code> with asyncpg, we may not have convenient access to the underlying asyncpg error.</p>
<h3 dir="auto">Databases / Backends / Drivers targeted</h3>
<p dir="auto">asyncpg</p>
<h3 dir="auto">Example Use</h3>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="try:
    # &lt;execute statement&gt;
except IntegrityError as ie:
    ..."><pre class="notranslate"><span class="pl-k">try</span>:
    <span class="pl-c"># &lt;execute statement&gt;</span>
<span class="pl-k">except</span> <span class="pl-v">IntegrityError</span> <span class="pl-k">as</span> <span class="pl-s1">ie</span>:
    ...</pre></div>
<p dir="auto">E.g. currently for a check constraint violation, <code class="notranslate">repr(ie.orig)</code> is</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="IntegrityError(&quot;&lt;class 'asyncpg.exceptions.CheckViolationError'&gt;: ...\nDETAIL:  ...&quot;)"><pre class="notranslate"><code class="notranslate">IntegrityError("&lt;class 'asyncpg.exceptions.CheckViolationError'&gt;: ...\nDETAIL:  ...")
</code></pre></div>
<p dir="auto">and <code class="notranslate">vars(ie.orig)</code> is simply <code class="notranslate">{'pgcode': '23514', 'sqlstate': '23514'}</code>.</p>
<p dir="auto">We would either want access to the actual <code class="notranslate">asyncpg.exceptions.CheckViolationError</code> object or we would want more of that object's fields copied into <code class="notranslate">ie.orig</code>. <a href="https://github.com/MagicStack/asyncpg/blob/bf74e88bb5193b39cf942a6f0e9b838da3d0214a/asyncpg/exceptions/_base.py#L27">This</a> appears to be a list of available fields (detail, table_name, etc.) although they may not all exist on every object, see <a href="https://github.com/sqlalchemy/sqlalchemy/discussions/6199#discussioncomment-571398" data-hovercard-type="discussion" data-hovercard-url="/sqlalchemy/sqlalchemy/discussions/6199/hovercard?comment_id=571398">here</a>.</p>
<p dir="auto">Currently the <a href="https://github.com/sqlalchemy/sqlalchemy/discussions/6199#discussioncomment-2006533" data-hovercard-type="discussion" data-hovercard-url="/sqlalchemy/sqlalchemy/discussions/6199/hovercard?comment_id=2006533">discussion</a> mentions a workaround involving <code class="notranslate">ie.orig.__cause__</code>.</p>
<h3 dir="auto">Additional context</h3>
<p dir="auto"><em>No response</em></p>
<h3 dir="auto">Discussed in <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="4074564" data-permission-text="Title is private" data-url="https://github.com/sqlalchemy/sqlalchemy/discussions/8023" data-hovercard-type="discussion" data-hovercard-url="/sqlalchemy/sqlalchemy/discussions/8023/hovercard" href="https://github.com/sqlalchemy/sqlalchemy/discussions/8023">#8023</a></h3>
<p dir="auto">I think we should try to hit this in terms of all the async dialects all of which have to do what we are doing here, which would be that we make a common mixin / base for all the async-adapted DBAPIs.  for asyncpg it would be used here: </p><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/19f0566dc98cc1d088777a9f1e1e7e898d861c05/lib/sqlalchemy/dialects/postgresql/asyncpg.py#L760-L761">sqlalchemy/lib/sqlalchemy/dialects/postgresql/asyncpg.py</a>
    </p>
    <p class="mb-0 color-fg-muted">
        Lines 760 to 761
      in
      <a data-pjax="true" class="commit-tease-sha" href="/sqlalchemy/sqlalchemy/commit/19f0566dc98cc1d088777a9f1e1e7e898d861c05">19f0566</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="L760" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="760"></td>
          <td id="LC760" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> <span class="pl-k">class</span> <span class="pl-v">Error</span>(<span class="pl-v">Exception</span>): </td>
        </tr>

        <tr class="border-0">
          <td id="L761" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="761"></td>
          <td id="LC761" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line">     <span class="pl-k">pass</span> </td>
        </tr>
    </tbody></table>
  </div>
</div>
<p></p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="    from ..exc import EmulatedDBAPIException

    class Error(EmulatedDBAPIException):
        pass"><pre class="notranslate">    <span class="pl-k">from</span> ..<span class="pl-s1">exc</span> <span class="pl-k">import</span> <span class="pl-v">EmulatedDBAPIException</span>

    <span class="pl-k">class</span> <span class="pl-v">Error</span>(<span class="pl-v">EmulatedDBAPIException</span>):
        <span class="pl-k">pass</span></pre></div>
<p dir="auto">that way people can look at <code class="notranslate">EmulatedDBAPIException</code> to see the fields, which would be like:</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="class EmulatedDBAPIException:
    &quot;&quot;&quot;an exception class used to adapt non-DBAPI database driver exceptions
    into DBAPI exceptions&quot;&quot;&quot;

    message: str
    &quot;&quot;&quot;string message indicating the error&quot;&quot;&quot;

    orig: Exception
    &quot;&quot;&quot;the original driver-level exception object&quot;&quot;&quot;"><pre class="notranslate"><span class="pl-k">class</span> <span class="pl-v">EmulatedDBAPIException</span>:
    <span class="pl-s">"""an exception class used to adapt non-DBAPI database driver exceptions</span>
<span class="pl-s">    into DBAPI exceptions"""</span>

    <span class="pl-s1">message</span>: <span class="pl-s1">str</span>
    <span class="pl-s">"""string message indicating the error"""</span>

    <span class="pl-s1">orig</span>: <span class="pl-v">Exception</span>
    <span class="pl-s">"""the original driver-level exception object"""</span></pre></div>
<p dir="auto">that looks kind of boring so we can also add an accessor on </p><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/19f0566dc98cc1d088777a9f1e1e7e898d861c05/lib/sqlalchemy/exc.py#L525">sqlalchemy/lib/sqlalchemy/exc.py</a>
    </p>
    <p class="mb-0 color-fg-muted">
         Line 525
      in
      <a data-pjax="true" class="commit-tease-sha" href="/sqlalchemy/sqlalchemy/commit/19f0566dc98cc1d088777a9f1e1e7e898d861c05">19f0566</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="L525" class="blob-num border-0 px-3 py-0 color-bg-default" data-line-number="525"></td>
          <td id="LC525" class="blob-code border-0 px-3 py-0 color-bg-default blob-code-inner js-file-line"> <span class="pl-k">class</span> <span class="pl-v">DBAPIError</span>(<span class="pl-v">StatementError</span>): </td>
        </tr>
    </tbody></table>
  </div>
</div>
 called "driver_exception".  This would work similarly to how we have "dbapi_connection" / "driver_connection" on Connection.<p></p>
<p dir="auto">that way someone that knows asyncpg can receive the top level SQLAlchemy DBAPIException and just refer to .driver_exception to get all the asyncpg fields.</p>