Holistic review round 2: fix all findings, release 1.7.1#18
Merged
Conversation
typeguard 4.1.0-4.1.2 do `from ast import Str` at import time; ast.Str was removed in Python 3.14 (still present in 3.13), so on 3.14 those releases -- and therefore `import encomp` -- fail with an opaque ImportError. All three ship py3-none-any wheels whose Requires-Python allows 3.14 and none is yanked, so a lowest-bounds resolution selects 4.1.0 on 3.14. Bisected empirically: 4.1.3 is the first release that imports on 3.14, and encomp works end-to-end with it (isinstance_types, the registered Quantity typeguard checker, self_check). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The fold previously combined pint's RAW >= / <= with the tolerant equality (`raw_ge | equal`). polars' total order calls NaN equal to NaN, so its raw `nan >= nan` is True while encomp's __eq__ says False -- on Series/Expr magnitudes `nan >= nan` and `nan <= nan` answered True with ==, > and < all False, breaking the documented `ge == (gt or eq)` decomposition within one container (the float path asserts the opposite result explicitly). Evaluate only the strict pint operator and derive the non-strict result as `(strict & ~equal) | equal`. For every non-NaN input this is equivalent to the previous fold (ties, +-inf and null propagation included, verified by fuzzing ~172k pairs per operator across all four containers); for nan-vs-nan it now answers False everywhere. The deliberate NaN-vs-number ordering divergence between the numpy and polars worlds is unchanged. nan-vs-nan joins _COMPARISON_PAIRS, which asserts container-independence for every operator. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The job resolved and ran the suite on 3.13 only. A pure-Python wheel installs on any Python, so --only-binary cannot stop a floor that resolves on both supported Pythons but is broken on one: typeguard 4.1.0 (the previous floor) imports ast.Str, which 3.14 removed -- the job passed on 3.13 while `import encomp` failed on 3.14. Cover the oldest and newest supported Python so a version-dependent floor fails in CI. Verified locally for the 3.14 leg: the lowest resolution picks typeguard==4.1.3 / pydantic==2.12.0 / pydantic-settings==2.0.0, and the installed suite passes (770 passed, 10 skipped). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ainers The mixed-container guard covered only numpy-vs-polars. A pl.Series quantity compared against a pl.Expr quantity fell through to polars' raw operator, which lifts the Series into an Expr literal and compares EXACTLY -- silently skipping the (rtol, atol) tolerance every sanctioned path applies (within-tolerance operands answered False), with a length mismatch surfacing only at collect() time as a ShapeError pointing into the plan. The pair is outside the typed API exactly like numpy-vs-polars, and the guard's stated principle is to refuse instead of picking silently. Treat any two DIFFERENT magnitude containers as mixed, and point the error at the right escape hatch: .astype() conversion for eager containers, or evaluating / explicitly pl.lit()-lifting when a pl.Expr is involved (an Expr has no data for .astype to convert). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A missing density yields a missing result at that position: NaN for float/numpy magnitudes, null for a Polars Series. A scalar NaN density (or an ndarray density with NaN rows) paired with a pl.Series input slipped between the container-specific guards and produced a Series result carrying NaN -- the sentinel a polars magnitude never uses, and the exact leak the Series-density branch rejects with 'use null for a missing density'. A float/ndarray density has no null spelling, so with a Polars input a NaN density cannot express 'missing': reject it with the same pointer at a null-carrying pl.Series. NaN densities remain the valid missing sentinel for float and numpy inputs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The note cited np.isclose, whose asymmetric predicate is precisely what the module-level _is_close exists to avoid; name the tolerance pair instead of an implementation the equality operator does not use. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…EADME The overview bullet credited Quantity.__new__ for the * / ` / ** result dimensionalities; __new__ overloads encode the unit literals, and the combinations are __mul__/__truediv__/__pow__ overloads, as the type-system section further down already states. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The versioning sections promised semantic versioning for documented public APIs; the release practice keeps those APIs stable where possible and announces any breaking change in the GitHub release notes, without reserving such changes for major versions. Say that. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Files section named fluid and humid_air as the package's public API; water is documented in the same README's API list above. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The committed state was an out-of-order interactive-session snapshot: execution counts 19, 20, 39, 21, ..., a stale 'extension is already loaded' stream in the setup cell, and no outputs for the %%pyinstrument cell. Re-execute sequentially (counts 1-14) and drop the pyinstrument profile display from the committed outputs -- it embeds machine-local filesystem paths -- keeping the cell's DataFrame result. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
automodule :members: only renders module data that carries an attribute docstring, so the exported constants (BACKENDS, FLUID_PARAMS, WATER_NAME, PHASE_IGNORING_BACKENDS, EAGER_PLUGIN_MIN_SIZE, CONSTANTS, SETTINGS, CUSTOM_DIMENSIONS, ...) were invisible on the rendered API page. Give each one a docstring. In encomp.units this alone was not enough: the rtol/atol comment wrapped onto a line starting with '# type:', which sphinx's source analyzer parses as a PEP 484 type comment -- the resulting parse failure silently dropped the attribute docstrings of the whole module. Rewrap the comment and leave a note, since nothing else guards against that. UNIT_REGISTRY stays undocumented data: autodoc's signature formatter fails on its UnitRegistry[Any] value annotation, which would break the -W docs build. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Patch release covering the holistic-review fixes: the typeguard floor raise, the ordering-operator derivation, the Series-vs-Expr comparison guard, the convert_volume_mass NaN-density rejection, and the docs and notebook refreshes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The raw.githubusercontent.com URL embedded the release tag, which made the README a second place that had to be edited on every version bump. Pointing it at main keeps the version in pyproject.toml only, and the absolute URL still renders on PyPI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Patch release fixing everything surfaced by the second holistic pre-release review (10 findings: 1 major, 3 minor, 6 nits — all reproduced at runtime before fixing). One commit per finding.
Fixes
Major
ast.Str, removed in 3.14), so the declared floor>=4.1was uninstallable on a supported Python. Bisected empirically: 4.1.3 is the first working release.--only-binarycannot catch a floor that resolves everywhere but only works on some interpreters. The 3.14 leg was pre-verified locally: resolves typeguard 4.1.3 / pydantic 2.12.0 / pydantic-settings 2.0.0, and the installed suite passes (770 passed, 10 skipped).Minor
nan >= nanreturned True (polars' total order calls NaN equal to NaN) while==said False, breaking the documentedge ⟺ gt ∨ eqconsistency.>=/<=are now derived asstrict ∨ eqfrom the same tolerant__eq__. Re-fuzzed ~172k value pairs per operator across all four magnitude containers;(nan, nan)added to the cross-container test matrix.pl.lit).convert_volume_massgets a Polars input — a NaNrhosentinel would flow into the Series/Expr result as NaN, violating the library-wide "missing = null, never NaN" convention for polars results. Now rejected with a message pointing at the null spelling.Nits
__hash__tolerance note at the (rtol, atol) predicate — the comment referencednp.isclose, which is not the predicate in use.Quantity.__new__overloads cover* / **inference; they are separate arithmetic-operator overloads.waterin the coolprop README file overview.encomp.coolpropconstants plusEAGER_PLUGIN_MIN_SIZE,CONSTANTS,SETTINGS,CUSTOM_DIMENSIONS. This surfaced a latent docs bug: a comment line starting with# type:inunits.pywas parsed by sphinx's ModuleAnalyzer as a PEP 484 type comment, silently dropping the attribute docs for the whole module.UNIT_REGISTRYis deliberately left undocumented — autodoc's signature formatter crashes on itsUnitRegistry[Any]annotation under-W.Release chores
maininstead of the release tag, so the version lives only in pyproject.toml.Gates (after the final commit)
-T -W --keep-going(RTD parity): build succeeded, constants render on the API page🤖 Generated with Claude Code