Skip to content

Bring library to parity with the JS reference v4.0.0 - #3

Open
lukemerrett wants to merge 2 commits into
httpie:mainfrom
Doist:js-v4-parity
Open

Bring library to parity with the JS reference v4.0.0#3
lukemerrett wants to merge 2 commits into
httpie:mainfrom
Doist:js-v4-parity

Conversation

@lukemerrett

@lukemerrett lukemerrett commented Jul 30, 2026

Copy link
Copy Markdown

What

Updates this library from its current state (equivalent to rocicorp/fractional-indexing ~v3.2.0) to parity with the JS reference v4.0.0, released June 2026.

  • Breaking (matches JS v4.0.0): the head alphabet now defaults to digits itself, so custom alphabets produce self-contained keys — generate_key_between(None, None, digits='0123456789') returns '50', not 'a0'. Keys generated with the default Base62 alphabet are unchanged. The new BASE_52_DIGITS export can be passed as int_digits to restore the previous A-Z/a-z heads.
  • New int_digits argument (JS v3.3.0) on generate_key_between(), generate_n_keys_between(), and validate_order_key() to customise the head alphabet; the hard-coded a-z/A-Z head logic is generalised accordingly.
  • Bounds may now be passed in either order and are swapped as a convenience (JS #26), instead of raising.
  • Alphabet validation (matches JS): at least 2 characters, strictly ascending character-code order, single-byte; int_digits must be even-length. Previously invalid alphabets were silently accepted and produced non-sorting keys; also, digits outside the alphabet leaked ValueError from str.index because the except IndexError guards were dead code. Everything now raises FIError.
  • Two deliberate improvements over the JS reference:
    • a defaulted int_digits is validated too, so an odd-length digits with no explicit int_digits raises FIError (the JS version silently generates broken keys in that case);
    • generate_n_keys_between() rejects a negative n with FIError — previously (in both this library and the JS reference) a negative n silently returned a single key when one bound was None, and recursed until RecursionError when both bounds were set.
  • Performance: cached per-alphabet digit-index dicts replace O(alphabet) str.index scans, and the midpoint uses integer arithmetic, dropping the decimal import (in the spirit of JS v3.4.0).

Testing

  • tests.py is now a full port of the JS v4.0.0 test suite (default, base-10 self-headed, base-95, base-2, Latin-1, symbol alphabets, int_digits overrides, validation errors) plus 1000-insert ordering property tests and negative-n cases — 87 tests.
  • Cross-language check: replayed 5 × 500-key random insertion sequences through both this branch and JS v4.0.0 — all 2,500 keys byte-for-byte identical across 5 alphabet configurations.
  • CI workflow updated to a Python 3.8–3.13 matrix.

Suggested release: 0.2.0 (version already bumped in fractional_indexing.py).

🤖 Generated with Claude Code

Port of rocicorp/fractional-indexing v4.0.0 (this library previously
matched ~v3.2.0):

- Breaking: the head alphabet (int_digits) now defaults to digits
  itself, so custom alphabets produce self-contained keys (base-10
  yields '50', not 'a0'). Keys from the default Base62 alphabet are
  unchanged. Pass the new BASE_52_DIGITS export as int_digits to keep
  the previous A-Z/a-z heads.
- New int_digits argument on generate_key_between(),
  generate_n_keys_between() and validate_order_key() to customise the
  head alphabet (JS v3.3.0 feature).
- Bounds may now be passed in either order and are swapped, instead of
  raising FIError (JS PR rocicorp/fractional-indexing#26).
- Alphabets are validated: >= 2 chars, strictly ascending char codes,
  single-byte; int_digits must be even-length. Previously invalid
  alphabets were silently accepted, and digits outside the alphabet
  leaked ValueError from str.index (the `except IndexError` guards were
  dead code). Everything now raises FIError.
- Unlike the JS reference, a defaulted int_digits is validated too: an
  odd-length digits alphabet with no explicit int_digits raises FIError
  instead of silently producing broken keys.
- Perf: per-alphabet digit-index dicts (cached) replace O(n) str.index
  scans; the midpoint uses integer arithmetic, dropping the decimal
  module (JS v3.4.0 spirit).
- Tests: full port of the JS v4.0.0 test suite, including ordering
  property tests; verified byte-for-byte identical to the JS reference
  across 2,500 generated keys in 5 alphabet configurations.
- CI: test matrix across Python 3.8-3.13.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lukemerrett added a commit to Doist/fractional-indexing-python that referenced this pull request Jul 30, 2026
Doist-fork-only changes on top of the js-v4-parity branch (which is
offered upstream as httpie#3):

- Replace poetry/setup.py with a hatchling pyproject; the distribution
  is renamed to doist-fractional-indexing (import name unchanged:
  fractional_indexing) so it can never shadow the public
  fractional-indexing name on the private index.
- Release workflow (workflow_dispatch) publishing to Doist's AWS
  CodeArtifact via uv publish, modeled on Doist/billist, plus the
  companion AWS_CODEARTIFACT_TOKEN refresh workflow (CI role ARN needs
  Platform provisioning - see TODO).
- README note explaining the fork and its relationship to upstream.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A negative n slipped past the n == 0 / n == 1 checks: with one bound
None the range() loop simply didn't run and the function silently
returned a single key; with both bounds set, mid = floor(n / 2) went
negative and the function recursed until RecursionError. Both the
silent wrong result and the 1000-frame traceback are worse than
enforcing the documented 'n >= 0' precondition with a clear FIError.

The JS reference (rocicorp/fractional-indexing) has the same behaviour
and only documents the precondition; this is a deliberate deviation,
like validating a defaulted int_digits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.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.

1 participant