Skip to content

chore: switch docs build to PyPI sphinx-pyrunblock instead of git+https - #221

Merged
taughz merged 2 commits into
masterfrom
chore/sphinx-pyrunblock-pypi
Oct 16, 2026
Merged

taughz merged 2 commits into
masterfrom
chore/sphinx-pyrunblock-pypi

Conversation

@petercorke

Copy link
Copy Markdown
Collaborator

Summary

sphinx-pyrunblock is Peter's own package (a hard fork of the original WhyNotHugo/sphinx-autorun, published properly to PyPI under this name). Today's CI installs it via pip install git+https://github.com/petercorke/sphinx-autorun.git -- an old, pre-rename URL that still works only because GitHub redirects renamed repos. This switches to installing the plain PyPI wheel instead.

  • Bumps pyproject.toml's docs extra from "sphinx-autorun" to "sphinx-pyrunblock" (the old entry was misleading in isolation -- without the workflow's follow-up git install, it would resolve to the unrelated upstream PyPI package of the same name, not Peter's fork).
  • Drops the now-redundant pip install git+... line from sphinx.yml, since pip install .[dev,docs] already installs the right package.
  • Updates conf.py's extensions list from sphinx_autorun to sphinx_pyrunblock to match.

No functional change

Both the old git URL and the new PyPI package resolve to the identical sphinx-pyrunblock==1.1.0 release today -- confirmed with a fresh, uncached pip install git+... and comparing installed package metadata. This is purely a speed/hygiene change.

Why

The PyPI install is consistently 2-4x faster in a clean venv (no git clone/VCS metadata resolution):

Method Run 1 Run 2
git+https://...sphinx-autorun.git 20.5s 10.6s
sphinx-pyrunblock (PyPI) 5.4s 7.1s

That's ~9-15s saved on every docs-build CI run (every push to master plus every PR).

Test plan

sphinx-pyrunblock is Peter's own package (published to PyPI, hard fork
of the original WhyNotHugo/sphinx-autorun), reachable at the same
content today via git+https://github.com/petercorke/sphinx-autorun.git
(an old, pre-rename URL that still redirects). Installing the PyPI
wheel directly is ~2-4x faster in CI (no git clone/VCS resolution) and
makes pyproject.toml's declared dependency honest -- previously
"sphinx-autorun" there would, in isolation, resolve to the unrelated
upstream PyPI package of that name rather than Peter's fork.

No functional change: both paths resolve to the same sphinx-pyrunblock
1.1.0 release. Verified with a real sphinx-build against a clean venv.
taughz pushed a commit that referenced this pull request Oct 16, 2026
…he root cause they shared

Found via the sphinx-pyrunblock rollout (PR #221): several `.. runblock::`
docstring examples were silently baking broken tracebacks into the
published docs instead of demonstrating real output.

## Twist2.unit() / Twist3.unit()

Branches were swapped (comments didn't match what the code under them
did), `self.w` had a `S.w` typo (undefined name), and the "prismatic"
branch passed a wrong-shape zero argument for Twist2 (3-element list
where a scalar is required). Fixed the branch logic, and use `abs()`
instead of `smb.norm()` for Twist2's scalar `w`.

## Twist2.pole

The docstring's own example called `S.pole()`, but `pole` is a
`@property`, not a method -- `TypeError: 'numpy.ndarray' object is not
callable`. (Twist3.pole's own example was already correct; only its
prose incorrectly said `X.pole()` too.)

## DualQuaternion: a shared root cause across three methods

Fixing `DualQuaternion.SE3()`'s docstring (called `d.T` instead of the
actual `d.SE3()` method) surfaced a real bug: `UnitQuaternion.conj()`
silently re-canonicalized its result's sign (forces scalar part >= 0),
which is correct for constructing a `UnitQuaternion` from arbitrary data
(q and -q are the same rotation) but wrong for conjugation, an algebraic
operation that must satisfy q*conj(q) == 1 for downstream algebra to be
correct.

Fixed `Quaternion.conj()` itself (spatialmath/quaternion.py) rather than
routing around it at each call site -- this is what "conjugate" should
mean regardless of caller. No existing test asserted the old
(re-canonicalizing) behaviour, only the return type.

This one fix resolved three separate, previously-untested DualQuaternion
bugs at once:
- `SE3()`: silently returned a transform with negated translation for
  any rotation with negative quaternion scalar part.
- `norm()`: crashed outright (`math domain error`) for the same reason
  -- also added a small floating-point clamp, since the norm-squared
  terms are mathematically non-negative but rounding can leave e.g.
  -1e-17 instead of exactly 0.
- Vector transformation (`dq * v`): found while re-verifying `norm()`'s
  fix -- unrelated to the conj() sign bug, this affected *every* case
  regardless of sign. The textbook q*P*conj(q) sandwich product's
  translation terms cancel to exactly zero under this class's own
  dual-part embedding convention (`__init__` builds `dual =
  0.5*Pure(t)*real`, translation on the left) -- silently applying only
  the rotation and dropping translation entirely. Since `SE3()` already
  correctly extracts (R, t) from this same embedding, reused it instead
  of hand-deriving a second, convention-specific sandwich formula.

## Test coverage

All of the above had zero prior test coverage (existing DualQuaternion
tests used SE3.Rx(pi/4) -- no translation, positive quaternion scalar
part -- which can't exercise any of these bugs). Added regression tests
for conj()'s sign behaviour (including a mixed-sign multi-valued case),
both branches of Twist2/Twist3.unit(), Twist2/Twist3.pole, and
DualQuaternion's SE3()/norm()/vector-transform with both a
negative-quaternion-scalar case and a general case.

Full suite: 334 passed (up from 326), 0 regressions. Real sphinx-build
verification (isolated venv, before/after): RUNBLOCK-ERROR count 6 -> 0,
warning count unchanged at 14 (confirmed stable across repeat builds of
both branches, ruling out build-to-build nondeterminism).
@taughz
taughz merged commit a77c7f0 into master Oct 16, 2026
13 checks passed
@taughz
taughz deleted the chore/sphinx-pyrunblock-pypi branch October 16, 2026 13:32
taughz pushed a commit that referenced this pull request Oct 16, 2026
…heir shared root cause) (#225)

## Summary

Found via the sphinx-pyrunblock rollout (#221): several `.. runblock::`
docstring examples were silently baking broken tracebacks into the
published docs instead of demonstrating real output.

**Twist2.unit() / Twist3.unit()** -- branches were swapped, `self.w` had
a `S.w` typo, and the "prismatic" branch passed a wrong-shape zero
argument for `Twist2` (3-element list where a scalar is required).

**Twist2.pole** -- the docstring's own example called `S.pole()`, but
`pole` is a `@property`, not a method.

**DualQuaternion: a shared root cause across three methods** -- fixing
`SE3()`'s docstring (called `d.T` instead of the actual `d.SE3()`
method) surfaced that `UnitQuaternion.conj()` silently re-canonicalized
its result's sign, which is correct for *constructing* a UnitQuaternion
(q and -q are the same rotation) but wrong for conjugation, an algebraic
operation that must satisfy `q*conj(q) == 1`. Fixed `Quaternion.conj()`
itself rather than routing around it at each call site. This one fix
resolved three separate, previously-untested bugs:
- `SE3()`: silently returned a transform with negated translation for
any rotation with negative quaternion scalar part.
- `norm()`: crashed outright (`math domain error`) for the same reason
-- also added a small floating-point clamp for the
mathematically-non-negative-but-can-round-negative norm-squared terms.
- Vector transformation (`dq * v`): unrelated to the sign bug --
affected *every* case regardless of sign. The textbook `q*P*conj(q)`
sandwich product's translation terms cancel to exactly zero under this
class's own dual-part embedding convention (`dual = 0.5*Pure(t)*real`).
Reused the already-correct `SE3()` extraction instead of hand-deriving a
second, convention-specific sandwich formula.

## Test coverage

All of the above had zero prior test coverage -- existing DualQuaternion
tests used `SE3.Rx(pi/4)` (no translation, positive quaternion scalar
part), which can't exercise any of these bugs. Added regression tests
for `conj()`'s sign behaviour, both branches of
`Twist2`/`Twist3.unit()`, `Twist2`/`Twist3.pole`, and `DualQuaternion`'s
`SE3()`/`norm()`/vector-transform.

## Test plan

- [x] Full suite: 334 passed (up from 326), 0 regressions
- [x] Real sphinx-build verification (isolated venv, before/after this
branch): RUNBLOCK-ERROR count 6 -> 0, warning count unchanged at 14
(confirmed stable across repeat builds of both branches, ruling out
build-to-build nondeterminism as an explanation either way)
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