Skip to content

🐛 Sort need links and backlinks naturally in needs.json and HTML#1695

Merged
ubmarco merged 5 commits into
masterfrom
mh-natural-sort
May 21, 2026
Merged

🐛 Sort need links and backlinks naturally in needs.json and HTML#1695
ubmarco merged 5 commits into
masterfrom
mh-natural-sort

Conversation

@ubmarco
Copy link
Copy Markdown
Member

@ubmarco ubmarco commented Apr 29, 2026

Summary

Closes #1371.

Need link and backlink lists are now naturally sorted (e.g. REQ_2 < REQ_9 < REQ_10) so build outputs are reproducible regardless of need load order, including when needs_external_needs is configured. Sorting happens unconditionally — no needs_reproducible_json flag is required — and applies to both needs.json and HTML output.

This matches the behaviour @ubmarco confirmed was desired: sort by default, in both outputs, irrespective of the needs_reproducible_json setting (which still keeps its existing role of stripping timestamps).

Implementation

  • NeedItem.sort_links() sorts _links, _backlinks and per-part backlinks in place using a natural sort key (digit runs are compared as ints, with the result alternating str/int so mixed-type comparisons are well-defined).
  • resolve_links() calls sort_links() on every need at the end of the post-processing pass, so all downstream consumers — JSON serialization in needsfile.py and the HTML rendering of outgoing/incoming refs in roles/need_outgoing.py and roles/need_incoming.py — see sorted lists.

Tests

New tests/test_needs_sort.py is YAML-driven and snapshot-based, modelled on tests/schema/test_schema.py:

  • tests/fixtures/sort_links.yml declares 5 cases:
    • alphabetical_outgoing — basic alphabetical sort of outgoing links
    • natural_outgoing — natural sort with digits (proves REQ_10 lands after REQ_9)
    • mixed_alphanumeric — mixed letter+number IDs
    • backlinks_sorted — backlinks naturally sorted (SPEC_1, SPEC_2, SPEC_10)
    • custom_link_types — sort works for custom link types (implements, derives)
  • For each case the test reads needs.json, derives link-field names from the embedded needs_schema (field_type ∈ {links, backlinks}), and snapshots the link/backlink fields per need. It also reads index.html, extracts (source need, target need) ref pairs by matching title="<source>" on link anchors, and snapshots that mapping. Both snapshots clearly show the natural ordering.

Existing snapshots in 8 unrelated test files were updated for the new sorted order; all changes are mechanical reorderings within link lists.

Test plan

  • CI passes
  • pytest tests/test_needs_sort.py — 5 cases pass, 10 snapshots match
  • pytest tests/ — no link-related regressions

Changelog

The bug-fix entry is in a new Unreleased section above 8.0.0 (now marked released on 19.03.2026).

@ubmarco ubmarco requested a review from chrisjsewell April 29, 2026 13:47
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 29, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.33%. Comparing base (4e10030) to head (b849faf).
⚠️ Report is 286 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1695      +/-   ##
==========================================
+ Coverage   86.87%   89.33%   +2.45%     
==========================================
  Files          56       72      +16     
  Lines        6532    10372    +3840     
==========================================
+ Hits         5675     9266    +3591     
- Misses        857     1106     +249     
Flag Coverage Δ
pytests 89.33% <100.00%> (+2.45%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ubmarco
Copy link
Copy Markdown
Member Author

ubmarco commented Apr 29, 2026

This PR is missing a performance analysis. Sorting links in big projects can be expensive.

Comment thread sphinx_needs/need_item.py
Comment thread sphinx_needs/need_item.py
ubmarco added a commit that referenced this pull request May 20, 2026
Address PR #1695 review comments from @chrisjsewell:

- `_natural_sort_key` now uses `tok.isdigit()` and `tok.casefold()` so
  the sort is case-insensitive (matches the ubcode helper).
- `NeedItem.sort_links()` collapses duplicate `NeedLink` entries (same
  id, part, condition) while sorting outgoing links, backlinks and
  part backlinks.

Snapshot updates fall out naturally:

- `test_dynamic_functions`: duplicate `CON_REQ_2` collapsed.
- `test_needimport`: `filter_IMPL_01` now sorts before `IMPL_01` /
  `T_C3893` under case-insensitive ordering.
ubmarco added 5 commits May 20, 2026 20:45
Need link and backlink lists are now naturally sorted (e.g.
``REQ_2`` < ``REQ_9`` < ``REQ_10``) so build outputs are reproducible
regardless of need load order, including when ``needs_external_needs`` is
configured. Sorting happens unconditionally — no ``needs_reproducible_json``
flag is required — and applies to both ``needs.json`` and HTML output.

Implementation: ``NeedItem.sort_links()`` sorts ``_links``, ``_backlinks``,
and per-part backlinks in place using a natural sort key that splits digit
runs and compares them as ints. ``resolve_links()`` calls this on every
need at the end of the post-processing pass, so all downstream consumers
(JSON serialization, HTML rendering of outgoing/incoming refs) see sorted
lists.

Tests: new ``tests/test_needs_sort.py`` with YAML-driven parametric cases
covering alphabetical/natural/mixed sorting, backlinks, and custom link
types — checks both the JSON and rendered HTML against snapshots.

Closes #1371
Adds two fixture cases with identical RST content but opposite values for
``needs_reproducible_json``. Their snapshots end up byte-identical, which
documents that sorting happens regardless of the flag.
Address PR #1695 review comments from @chrisjsewell:

- `_natural_sort_key` now uses `tok.isdigit()` and `tok.casefold()` so
  the sort is case-insensitive (matches the ubcode helper).
- `NeedItem.sort_links()` collapses duplicate `NeedLink` entries (same
  id, part, condition) while sorting outgoing links, backlinks and
  part backlinks.

Snapshot updates fall out naturally:

- `test_dynamic_functions`: duplicate `CON_REQ_2` collapsed.
- `test_needimport`: `filter_IMPL_01` now sorts before `IMPL_01` /
  `T_C3893` under case-insensitive ordering.
@ubmarco ubmarco force-pushed the mh-natural-sort branch from d3a0f2d to b849faf Compare May 20, 2026 18:58
@ubmarco ubmarco requested a review from chrisjsewell May 20, 2026 18:59
@ubmarco
Copy link
Copy Markdown
Member Author

ubmarco commented May 20, 2026

Performance impact analysis

Project under test

  • Files: 200 RST source files
  • Needs: 1022, spread over 13 need types
  • Fields per need: 174 — 36 core, 130 extra options, 8 outgoing-link relations
  • Outgoing-link density: mean 3.11 links/need (median 3, min 0, max 40)

Each outgoing-link relation also produces a computed backlink list per target need; this PR's natural-sort change applies to both outgoing-link lists and the computed backlinks, both of which are reflected in needs.json.

Both runs use the same input corpus and produce the same needs.json — only the order of links/backlinks differs between baseline and PR, as expected.

Methodology

  • Baseline: sphinx-needs tag 8.1.1
  • Under test: this PR (mh-natural-sort)
  • Sphinx: 8.x
  • Builder: sphinx-build -W -b html — wall clock of the build subprocess only
  • Iterations: 5 per branch, sequential, single local Linux x86_64 host

Results

Revision n mean median stdev min max
8.1.1 (baseline) 5 52.71 s 52.49 s 1.337 s 51.32 s 54.42 s
mh-natural-sort (PR) 5 52.69 s 51.72 s 1.506 s 51.48 s 54.84 s

Δ (PR − baseline): −0.03 s (−0.05 %) — well inside the run-to-run noise (stdev ≈ 1.4 s, ~2.6 % of mean). No measurable regression at this corpus size.

@ubmarco ubmarco merged commit 0375eb5 into master May 21, 2026
25 checks passed
@ubmarco ubmarco deleted the mh-natural-sort branch May 21, 2026 11:53
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.

Need links are not sorted in needs.json

2 participants