Skip to content

Honor falsey AdapterFactory subclasses - #358

Closed
harbinresearcher wants to merge 1 commit into
GrahamDumpleton:developfrom
harbinresearcher:codex/falsey-adapter-factory
Closed

harbinresearcher wants to merge 1 commit into
GrahamDumpleton:developfrom
harbinresearcher:codex/falsey-adapter-factory

Conversation

@harbinresearcher

Copy link
Copy Markdown
Contributor

Falsey subclasses of the public AdapterFactory were silently skipped by wrapt.decorator, leaving the wrapped function's original signature in place. Check for an adapter factory before evaluating the adapter's truth value, while retaining the existing behavior for None, False, and empty-string adapters.

Add regressions for factories using __bool__ or __len__, checking invocation, signature, argspec, and normal calls. Add compatibility cases for disabled adapters and an unreleased change note.

Validation:

  • Both new factory cases fail on the original code.
  • Linux: the complete just test matrix passes (27 runs across Python 3.9–3.15, including 3.14t/3.15t, with all three extension variants and the pinned mypy).
  • Windows: full core suite passes on Python 3.9–3.14; Python 3.12 full tests including pinned mypy: 1394 passed, 8 skipped.
  • Black checks, strict Sphinx build, source/wheel build, and package metadata checks pass.
  • Independent agent review found no blocking issues.

Linux used a local Zig compiler adapter because GCC was unavailable; it filters the unsupported GNU --exclude-libs,ALL linker flag and isolates the compiler's Python environment. No project configuration or dependencies were changed. PyPy was not checked locally. AI assistance was used for implementation and verification.

@GrahamDumpleton

GrahamDumpleton commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

I'll have a look, but be warned that AdpaterFactory and adapter argument to decorator is now deprecated. Instead look at with_signature and with_doc decorators. Can you provide an example of what you were trying to do so I can determine whether the new way of doing things will suit your requirements.

Also see the "Changing the signature" workshop as part of the set of workshops for wrapt.

@GrahamDumpleton

Copy link
Copy Markdown
Owner

Thanks for finding this and for the thorough write up. The bug is real: an
AdapterFactory instance which evaluates as false was being skipped because
the truth value of the adapter was tested before checking whether it was a
factory.

I have applied a fix directly on develop in 77e826d rather than merging
this PR, for two reasons.

First, I restructured the conditional. Rather than testing
isinstance(adapter, AdapterFactory) or adapter and then repeating the
isinstance check inside, the factory is now resolved by type first and the
truth value of the result is tested afterwards. That removes the duplicated
check and reads as the intended logic rather than a patch over it.

Second, that restructuring changes one additional behaviour. A factory which
returns None, or another false value, now means no adapter is to be used for
that function. Previously it fell into the argspec handling and failed with
an obscure TypeError. The change note and tests cover this new case as well
as the ones from this PR.

The tests here were adapted into the commit, and the change note sits under
the 2.5.1 section with credit to you. You are also recorded as a co-author
on the commit. Closing this in favour of that commit.

GrahamDumpleton added a commit that referenced this pull request Oct 12, 2026
…s not ignored.

A wrapt.AdapterFactory instance which defined __bool__() or __len__() such
that it evaluated as false was skipped and never called, leaving the
decorated function with its original signature. The factory is now
resolved by type before the truth value of the adapter is tested. As a
consequence a factory may now return None, or another false value, to
indicate no adapter should be used for a particular function.

Supersedes pull request #358.

Co-authored-by: Luochenchen <hsyjy01@gmail.com>
4alvit added a commit to ha-homelab/ha-echo-dot that referenced this pull request Oct 20, 2026
Bumps [wrapt](https://github.com/GrahamDumpleton/wrapt) from 2.5.0 to
2.5.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/GrahamDumpleton/wrapt/releases">wrapt's
releases</a>.</em></p>
<blockquote>
<h2>wrapt 2.5.1</h2>
<p>Full release notes: <a
href="https://wrapt.readthedocs.io/en/latest/changes.html#version-2-5-1">https://wrapt.readthedocs.io/en/latest/changes.html#version-2-5-1</a></p>
<p>Install from PyPi (recommended):</p>
<pre><code>pip install wrapt==2.5.1
</code></pre>
<p>PyPi uploads follow each GitHub release; if <code>pip</code> reports
the
version is unavailable, the matching PyPi upload may not have
happened yet.</p>
<p>Pre-built wheels are provided for a range of Python versions
and platforms (Linux x86_64/aarch64/riscv64, macOS x86_64 and
arm64, Windows x86_64 and arm64, plus PyPy and free-threaded
builds). The source distribution is also attached together
with <code>SHA256SUMS</code> for verification.</p>
<h2>wrapt 2.5.1rc1</h2>
<p>Release candidate. Release notes for the upcoming
2.5.1 final (work in progress): <a
href="https://wrapt.readthedocs.io/en/latest/changes.html#version-2-5-1">https://wrapt.readthedocs.io/en/latest/changes.html#version-2-5-1</a></p>
<p>May be installable from PyPi:</p>
<pre><code>pip install wrapt==2.5.1rc1
</code></pre>
<p>If <code>pip</code> reports the version is unavailable, this
candidate
either has not been uploaded yet or is not being published to
PyPi. Use the attached wheels or build from the source
distribution instead:</p>
<pre><code>tar xf wrapt-2.5.1rc1.tar.gz
cd wrapt-2.5.1rc1
pip install .
</code></pre>
<p><code>SHA256SUMS</code> is attached for verification of the
archives.</p>
<h2>wrapt 2.5.1.dev1</h2>
<p>Development snapshot. Not normally published to PyPi
(occasionally uploaded to exercise the release pipeline).</p>
<p>Release notes for the upcoming 2.5.1 final (work in
progress): <a
href="https://wrapt.readthedocs.io/en/latest/changes.html#version-2-5-1">https://wrapt.readthedocs.io/en/latest/changes.html#version-2-5-1</a></p>
<p>Platform wheels and the source distribution are attached. To
test against your Python install, either grab a matching wheel
or build from the source distribution:</p>
<pre><code>pip install
wrapt-2.5.1.dev1-&lt;python&gt;-&lt;abi&gt;-&lt;platform&gt;.whl
</code></pre>
<p>or</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/GrahamDumpleton/wrapt/blob/develop/docs/changes.rst">wrapt's
changelog</a>.</em></p>
<blockquote>
<h2>Version 2.5.1</h2>
<p><strong>Bugs Fixed</strong></p>
<ul>
<li>
<p>A <code>LazyObjectProxy</code> created with an <code>interface</code>
argument that
declared a path-like type, such as <code>os.PathLike</code> or
<code>pathlib.Path</code>,
did not define the <code>__fspath__()</code> special method on the class
it
generated. Because <code>os.fspath()</code> looks up
<code>__fspath__()</code> on the type
of the object, passing such a proxy to <code>os.fspath()</code>, the
builtin
<code>open()</code> or other standard library functions accepting paths
raised
<code>TypeError</code> before the factory callable had been invoked to
create the
wrapped object. The <code>__fspath__()</code> special method is now
added when the
declared interface provides it, in the same way as is already done for
the other special methods looked up on the type, so the first path
conversion triggers creation of the wrapped object and succeeds. With
thanks to Muneeb Sheikh for <code>pull request
[#360](GrahamDumpleton/wrapt#360)
&lt;https://github.com/GrahamDumpleton/wrapt/pull/360&gt;</code>_.</p>
</li>
<li>
<p>A callback passed to <code>wrapt.WeakFunctionProxy</code> which was
callable but
evaluated as false, such as an event emitter object which reports its
number of subscribers via <code>__len__()</code> and is empty when first
created,
was passed the underlying weak reference object instead of the proxy
when the wrapped function expired. Where the wrapped function was a
bound method, such a callback could also be called twice, once each
when the instance and the function expired. Only <code>None</code> is
now treated
as meaning no callback was supplied, which mirrors the behaviour of
<code>weakref.ref()</code> and <code>weakref.proxy()</code> in the
standard library, so
these callbacks now receive the proxy and are called only once, as for
any other callback. With thanks to Huanyi Xie for <code>pull request
[#359](GrahamDumpleton/wrapt#359)
&lt;https://github.com/GrahamDumpleton/wrapt/pull/359&gt;</code>_.</p>
</li>
<li>
<p>A <code>wrapt.AdapterFactory</code> instance passed as the
<code>adapter</code> argument
to <code>wrapt.decorator</code> was ignored, and so never called, if the
instance
evaluated as false, such as when the factory class defined
<code>__bool__()</code>
or <code>__len__()</code>. The decorated function then retained its
original
signature instead of that produced by the factory. Whether an adapter
factory was supplied is now determined by type before the truth value
of the adapter is considered, so such factories are always called. As a
consequence of this change a factory can now also return
<code>None</code>, or
any other false value, to indicate that no adapter should be used for a
particular function, where previously this failed with an obscure error.
With thanks to Luochenchen for <code>pull request
[#358](GrahamDumpleton/wrapt#358)
&lt;https://github.com/GrahamDumpleton/wrapt/pull/358&gt;</code>_ which
identified
the issue.</p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/GrahamDumpleton/wrapt/commit/dbbb21fa76915541a850a2029e97c116f38a53b0"><code>dbbb21f</code></a>
Merge branch 'release/2.5.1'</li>
<li><a
href="https://github.com/GrahamDumpleton/wrapt/commit/95e2afbb47cadfdff5e19ec88ba7caf718bd4a45"><code>95e2afb</code></a>
Drop the release candidate suffix for the 2.5.1 release.</li>
<li><a
href="https://github.com/GrahamDumpleton/wrapt/commit/1ed68ca8c32b15c59dea50032b08bc3271529ec3"><code>1ed68ca</code></a>
Update to 2.5.1rc1.</li>
<li><a
href="https://github.com/GrahamDumpleton/wrapt/commit/77e826dd8973c960f0733977ec67f43896a4d050"><code>77e826d</code></a>
Recognise adapter factories by type so one which evaluates as false is
not ig...</li>
<li><a
href="https://github.com/GrahamDumpleton/wrapt/commit/13efe635b6942394977b016cfd2d8ac6530a8124"><code>13efe63</code></a>
Move change note for PR <a
href="https://redirect.github.com/GrahamDumpleton/wrapt/issues/359">#359</a>
under 2.5.1 and add clarifying comments.</li>
<li><a
href="https://github.com/GrahamDumpleton/wrapt/commit/6ba6048628e7a1c584bd767b451037dc36dd6b03"><code>6ba6048</code></a>
Reorder <strong>fspath</strong> handling in lazy proxy and add change
note for PR <a
href="https://redirect.github.com/GrahamDumpleton/wrapt/issues/360">#360</a>.</li>
<li><a
href="https://github.com/GrahamDumpleton/wrapt/commit/5e97e2d1a3c2effc9f1f8357f83802e6cbce8998"><code>5e97e2d</code></a>
Update to 2.5.1.dev1.</li>
<li><a
href="https://github.com/GrahamDumpleton/wrapt/commit/e9d491c7c8b31747e9dc280f48783d69d9ac2904"><code>e9d491c</code></a>
Merge pull request <a
href="https://redirect.github.com/GrahamDumpleton/wrapt/issues/359">#359</a>
from xiehuanyi/fix-falsey-weak-proxy-callbacks</li>
<li><a
href="https://github.com/GrahamDumpleton/wrapt/commit/0d6c935bac4fe9c4b3ca2a0f696950024b8b07bc"><code>0d6c935</code></a>
Merge pull request <a
href="https://redirect.github.com/GrahamDumpleton/wrapt/issues/360">#360</a>
from muneikh/fix-lazy-pathlike-interface</li>
<li><a
href="https://github.com/GrahamDumpleton/wrapt/commit/de574aa4fd5ca3c272e533dfd250a4868bfacbb3"><code>de574aa</code></a>
Honor the declared pathlike interface in lazy proxies</li>
<li>Additional commits viewable in <a
href="https://github.com/GrahamDumpleton/wrapt/compare/2.5.0...2.5.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=wrapt&package-manager=pip&previous-version=2.5.0&new-version=2.5.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: alvit <272257197+4alvit@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants