Add __trunc__ to ObjectProxy so math.trunc() works transparently - #344
Conversation
ObjectProxy already implements __round__ (so round() works), but was
missing __trunc__, causing math.trunc() to raise TypeError for any
wrapped numeric type:
>>> import wrapt, math
>>> math.trunc(wrapt.ObjectProxy(1.7))
TypeError: type ObjectProxy doesn't define __trunc__ method
In Python 3.11+ math.trunc() looks up __trunc__ via the type (not
instance), so ObjectProxy.__getattr__ forwarding does not help. Add an
explicit __trunc__ that delegates to math.trunc(self.__wrapped__),
mirroring the pattern already used by __round__ / round().
Also add three regression tests covering float, negative float, and
fractions.Fraction wrapped values.
|
The Note though that the change proposed doesn't include C API implementations which if the tests work without, suggests that |
|
Thanks, that makes sense. I updated the PR to cover the full math integral protocol surface:
Verification:
|
|
I'll merge this, but going to make a few changes afterwards and add more tests. |
Rename C helper function to follow naming convention for internal helpers, add conventional unused second argument to METH_NOARGS functions, extend test coverage for __floor__ and __ceil__ including precedence over __float__ fallback, and add change notes.
…3323) Bumps the py-major group in /python with 2 updates: [wrapt](https://github.com/GrahamDumpleton/wrapt) and [multipart](https://github.com/defnull/multipart). Updates `wrapt` from 1.17.3 to 2.3.0 <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.3.0</h2> <p>Full release notes: <a href="https://wrapt.readthedocs.io/en/latest/changes.html#version-2-3-0">https://wrapt.readthedocs.io/en/latest/changes.html#version-2-3-0</a></p> <p>Install from PyPi (recommended):</p> <pre><code>pip install wrapt==2.3.0 </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.3.0rc2</h2> <p>Release candidate. Release notes for the upcoming 2.3.0 final (work in progress): <a href="https://wrapt.readthedocs.io/en/latest/changes.html#version-2-3-0">https://wrapt.readthedocs.io/en/latest/changes.html#version-2-3-0</a></p> <p>May be installable from PyPi:</p> <pre><code>pip install wrapt==2.3.0rc2 </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.3.0rc2.tar.gz cd wrapt-2.3.0rc2 pip install . </code></pre> <p><code>SHA256SUMS</code> is attached for verification of the archives.</p> <h2>wrapt 2.3.0rc1</h2> <p>Release candidate. Release notes for the upcoming 2.3.0 final (work in progress): <a href="https://wrapt.readthedocs.io/en/latest/changes.html#version-2-3-0">https://wrapt.readthedocs.io/en/latest/changes.html#version-2-3-0</a></p> <p>May be installable from PyPi:</p> <pre><code>pip install wrapt==2.3.0rc1 </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.3.0rc1.tar.gz </code></pre> <!-- 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.3.0</h2> <p><strong>New Features</strong></p> <ul> <li> <p>The <code>__trunc__()</code>, <code>__floor__()</code> and <code>__ceil__()</code> special methods are now implemented by object proxies, delegating to <code>math.trunc()</code>, <code>math.floor()</code> and <code>math.ceil()</code> applied to the wrapped object. As with other special methods, these are only looked up on the class type and not the instance, so they cannot rely on the <code>__getattr__()</code> fallback of the proxy and must be implemented explicitly. Previously calling <code>math.trunc()</code> on an object proxy raised <code>TypeError</code>. These special methods sit somewhat outside the core Python object model in that they are not used by any builtin operators, with the <code>math</code> module being their only consumer. They are however documented as part of the Python data model and the <code>math</code> module is a key module in the standard library, so supporting them is warranted, in the same way as the existing support for <code>__round__()</code>, which is consumed by the <code>round()</code> builtin. Note that although <code>math.floor()</code> and <code>math.ceil()</code> previously appeared to work when used on an object proxy, they were silently falling back to converting the proxy using <code>__float__()</code>. If the wrapped object provided its own <code>__floor__()</code> or <code>__ceil__()</code> special methods these were ignored and the result could differ from that when the wrapped object was used directly. These now yield the same result as using the wrapped object directly. With thanks to Vincent Gao for <code>pull request [#344](GrahamDumpleton/wrapt#344) <https://github.com/GrahamDumpleton/wrapt/pull/344></code>_.</p> </li> <li> <p>The <code>__fspath__()</code> special method of the <code>os.PathLike</code> protocol has been added to the set of dunder methods which <code>AutoObjectProxy</code> detects on the wrapped object and adds to the class it generates, so a proxy it creates around a path-like object can now be used with <code>os.fspath()</code>, the builtin <code>open()</code> and other standard library functions accepting paths. Note that <code>__fspath__()</code> is deliberately not implemented by the base object proxy, since its presence on the proxy type would cause every proxy to be classified as path-like by code branching on <code>isinstance(obj, os.PathLike)</code>. Also be aware that <code>AutoObjectProxy</code> creates a new class for every proxy instance, so it should not be used to wrap path-like objects in large numbers due to the memory overhead. For high-frequency use define a custom proxy class which adds an explicit <code>__fspath__()</code> method instead. See the section on wrapping path-like objects in the known issues documentation for more details.</p> </li> </ul> <p><strong>Features Changed</strong></p> <ul> <li>The type stubs have been aligned with the runtime behaviour of the code and are now verified by <code>stubtest</code> against both the C extension and pure Python implementations. If using a type checker there are a couple of changes in what will be accepted which may be noticed. The</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/GrahamDumpleton/wrapt/commit/95d5d01bd671186000c02e1d0f6806a4d7c5d217"><code>95d5d01</code></a> Merge branch 'release/2.3.0'</li> <li><a href="https://github.com/GrahamDumpleton/wrapt/commit/de094674f4d5e59b1b69c3b042ecc9b0eac729e7"><code>de09467</code></a> Update to 2.3.0 for final release.</li> <li><a href="https://github.com/GrahamDumpleton/wrapt/commit/f2f807e002e6fab134cb7599b34b93cadc900bf1"><code>f2f807e</code></a> Update to 2.3.0rc2.</li> <li><a href="https://github.com/GrahamDumpleton/wrapt/commit/4f6223ef946941e68352752dee1815bb2cb6ec9c"><code>4f6223e</code></a> Acknowledge reporter in bytes() bug fix change log entry.</li> <li><a href="https://github.com/GrahamDumpleton/wrapt/commit/783d05877279e51d288466c0c0b37487c2034cf2"><code>783d058</code></a> Extend tests for bytes() conversion of object proxies.</li> <li><a href="https://github.com/GrahamDumpleton/wrapt/commit/300d23d406ff929d169663d8f98347271add017d"><code>300d23d</code></a> Merge pull request <a href="https://redirect.github.com/GrahamDumpleton/wrapt/issues/345">#345</a> from Sanjays2402/fix/bytes-proxy-int</li> <li><a href="https://github.com/GrahamDumpleton/wrapt/commit/6cf696233e4d777cda88c6ee06053bf71f73a445"><code>6cf6962</code></a> Make bytes() on a proxy match bytes() on the wrapped object</li> <li><a href="https://github.com/GrahamDumpleton/wrapt/commit/58557a7cf87e325db675c9dfdf218600bec77daa"><code>58557a7</code></a> Update to 2.3.0rc1.</li> <li><a href="https://github.com/GrahamDumpleton/wrapt/commit/2fe996189f298f7951aef75e1b63a9a585f1cff3"><code>2fe9961</code></a> Document buffer protocol behaviour of object proxies.</li> <li><a href="https://github.com/GrahamDumpleton/wrapt/commit/818191bef9075a07eea1092446123d2fdc5c6af6"><code>818191b</code></a> Force and verify C extension build in test-stubtest recipe.</li> <li>Additional commits viewable in <a href="https://github.com/GrahamDumpleton/wrapt/compare/1.17.3...2.3.0">compare view</a></li> </ul> </details> <br /> Updates `multipart` from 1.3.1 to 2.0.0 <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/defnull/multipart/blob/main/CHANGELOG.rst">multipart's changelog</a>.</em></p> <blockquote> <h1>========= Changelog</h1> <p>This project follows Semantic Versioning (<code>major.minor.patch</code>), with the exception that behavior changes are allowed in minor releases as long as the change corrects behavior to match documentation, specification or expectation. In other words: Bugfixes do not count as backward incompatible changes, even if they technically change behavior from <em>incorrect</em> to <em>correct</em> and may break applications that rely on <em>incorrect</em> or <em>undefined</em> behavior or <em>undocumented</em> APIs. EOL Python versions may also be dropped during <em>minor</em> releases.</p> <h1>Release 2.0</h1> <p>This release drops EOL python versions, improves error handling and includes multiple performance improvements that can result in up to 30% faster file uploads and 50% faster text field handling.</p> <ul> <li>change: Dropped support for Python 3.8 and 3.9 (both EOL), added support for Python 3.14</li> <li>change: Accessing :attr:<code>MultipartSegment.size</code> before the segment is complete now raises :exc:<code>ParserStateError</code>. The new attribute :attr:<code>MultipartSegment.bytes_received</code> provides the in-progress segment body byte count previously exposed by :attr:<code>MultipartSegment.size</code>.</li> <li>change: :class:<code>PushMultipartParser</code> now emits :class:<code>bytes</code> instead of :class:<code>bytearray</code> for part body chunks. Update your <code>isinstance</code> checks if you have any.</li> <li>change: Enforce <code>part_limit</code> (128 by default) for url-encoded data in <code>parse_form_data()</code>. This is consistent with multipart handling and an important safeguard against denial of service attacks.</li> <li>change: New strict-mode check to reject extremely large boundaries.</li> <li>change: Raise more helpful :exc:<code>ParserStateError</code> instead of implicit :exc:<code>AssertionError</code> or :exc:<code>TypeError</code> when the parser is used incorrectly.</li> <li>change: The boundary delimiter must not appear inside a segment body (RFC-7578 4.1). This triggers a fatal error now.</li> <li>feat: Hardened (and faster) header validation.</li> <li>build: Change default branch to <code>main</code>.</li> </ul> <h1>Release 1.3</h1> <p>This release adds new APIs and contains internal or non-breaking api changes.</p> <ul> <li>feat: New convenience methods :meth:<code>PushMultipartParser.parse_blocking</code> and :meth:<code>PushMultipartParser.parse_async</code>.</li> <li>feat: Nicer error messages when reading from a closed <code>MultipartPart</code>.</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/defnull/multipart/commit/a14fdd637f1a69b6e8db06603489ac495efd1bb2"><code>a14fdd6</code></a> Release of 2.0.0</li> <li><a href="https://github.com/defnull/multipart/commit/dca7836b27b40aec62cd9efbb0ab2f40be0a8b29"><code>dca7836</code></a> Prepare 2.0 release</li> <li><a href="https://github.com/defnull/multipart/commit/1240e8cb74b2473834b76d6096486ae70e59ba79"><code>1240e8c</code></a> build: fix venv builder in makefile</li> <li><a href="https://github.com/defnull/multipart/commit/ba1d834d3884298e4539b58a8eff2532737bcb71"><code>ba1d834</code></a> change: MultipartSegment.size now throws if incomplete.</li> <li><a href="https://github.com/defnull/multipart/commit/59322fee206c150211f84f72769d20f34acb9666"><code>59322fe</code></a> build: More github CI checks and fixed makefile</li> <li><a href="https://github.com/defnull/multipart/commit/4421f32f1674bd0d0b1dea40ce23c2e6da566307"><code>4421f32</code></a> Switch to stdlib cached_property</li> <li><a href="https://github.com/defnull/multipart/commit/520fdf9d2409958589d61fff621aacd190983930"><code>520fdf9</code></a> All public APIs should be in <strong>all</strong></li> <li><a href="https://github.com/defnull/multipart/commit/42e7ea77db397ef3579c0efa09e5f70c4d2a2721"><code>42e7ea7</code></a> docs: cleanup</li> <li><a href="https://github.com/defnull/multipart/commit/bce8554f7a6b469a75531898f685dcbb3ae70b39"><code>bce8554</code></a> feat: parse_content_disposition is now a publich API</li> <li><a href="https://github.com/defnull/multipart/commit/e1c47df9af65279e0c55acd5605b80d0f1c29ea7"><code>e1c47df</code></a> Link to defnull.de for benchmarks</li> <li>Additional commits viewable in <a href="https://github.com/defnull/multipart/compare/v1.3.1...v2.0.0">compare view</a></li> </ul> </details> <br /> 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 <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Summary
ObjectProxyalready implements__round__(soround()works onproxied numeric objects), but
__trunc__was absent. In Python 3.11+math.trunc()looks up__trunc__via the type, not the instance,so
ObjectProxy.__getattr__forwarding does not help:This is inconsistent:
round(proxy)works viaObjectProxy.__round__math.ceil(proxy)/math.floor(proxy)happen to work via CPython's__float__fallback in those builtinsmath.trunc(proxy)fails with aTypeErrorFix
Add
__trunc__to_ObjectProxyMethods, mirroring the__round__/round()pattern:Three regression tests are included covering
float, negativefloat,and
fractions.Fractionproxied values.Relation to issue #296
Issue #296 listed
__trunc__as a missing method and was closedwithout implementing it. This PR provides a minimal, focused fix with
test coverage.
This pull request was prepared with the assistance of AI, under my direction and review.