feat(python,resolver): package-orderer plugin SDK#100
Open
doubleailes wants to merge 1 commit into
Open
Conversation
`rer` hardcoded rez's default `SortedOrder(descending=True)` — the solver always preferred the highest version of a family, by rez's native alphanumeric-token comparison. A studio running a custom rez orderer (e.g. Fortiche's PEP 440 `Pep440Orderer`) therefore picked different versions than `rer` — the root of the version-selection divergence reported in #96. This adds a plugin SDK so a host can override the version preference, mirroring rez's own orderer model (an SDK base class + an explicit registry — not rez's heavyweight plugin-manager discovery, which orderers don't use either). ## Python SDK `pyrer` is now a mixed Rust+Python package: the compiled extension moved to the `pyrer._native` submodule; a pure-Python `pyrer` package wraps it and hosts the SDK. The restructure is transparent — `import pyrer` and every existing symbol are unchanged for callers; `pyrer.__version__` is now available. - `pyrer.PackageOrderer` — subclass it, set `name`, implement `order(family, versions) -> list[str]` (versions reordered most-preferred-first). - `pyrer.register_orderer(MyOrderer)` — register a subclass or instance under its `name`. - `pyrer.solve(..., package_orderer="<name>")` — select by registered name, or pass a `PackageOrderer` instance, or `None` for the default. The orderer is a *preference* function: it changes which solution is found, never whether a solve succeeds. A misbehaving orderer is handled defensively — a version omitted from the result sinks to the bottom, a version not in the input is ignored. A raising `order()` surfaces as `status="error"`; no exception escapes `pyrer.solve`. ## Rust core The single solver-visible version-order site, `PackageVariantSlice:: sort_versions()`, now keys on a per-version `order_rank` (0 = most preferred) instead of `version.cmp` descending. `order_rank` is computed once per family in `PackageVariantList::new`: - No orderer → rank = version-descending position (bit-identical to the previous hardcoded behaviour). - Orderer set → the `FamilyOrderer` callback is invoked once with the family's version strings; `build_rank_map` turns its output into ranks, defensively (omitted versions sink, unknowns ignored, never panics). New `FamilyOrderer` callback type and `SolverContext.package_order` field (with a `with_package_order` builder and a manual `Debug` impl — `Rc<dyn Fn>` isn't `Debug`). `Solver::new_with_options` gains a 5th `package_order` parameter; `new` / `new_with_cache` pass `None`. The orderer is re-invoked on every `PackageVariantList` (re)build, including the issue #92 widened-range reload path — ranks are always recomputed wholesale, no stale ranks survive. ## Tests - Rust: 5 `build_rank_map` tests (permutation, omitted-sink, unknown-ignored, duplicate-first, empty-output) + 4 solver tests (reverse-preference picks lowest, pin-a-version, partial-output no-panic, no-orderer-control picks highest). 55/55 resolver unit tests pass. - Python: 10 new tests — register+select-by-name, select-by-instance, unknown name → ValueError, no-`name` → ValueError, non-orderer → TypeError, `order()` raises → status="error", partial output, `None` is default. 121/121 `pytest tests/` pass. - Strict 188-case rez differential: 188/188 with no orderer set — default ordering unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
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.
Summary
Adds a package-orderer plugin SDK to
pyrer, so a host can overridewhich version of a family the solver prefers.
rerpreviously hardcodedrez's default
SortedOrder(descending=True); a studio running a custom rezorderer (e.g. Fortiche's PEP 440
Pep440Orderer) therefore diverged fromreron version selection — the root of issue #96.The design mirrors rez's own orderer model — an SDK base class + an
explicit registry — not rez's heavyweight
plugin_managersdiscovery (whichorderers don't use either).
Python SDK
pyreris now a mixed Rust+Python package: the compiled extension movedto the
pyrer._nativesubmodule; a pure-Pythonpyrerpackage wraps it andhosts the SDK. The restructure is transparent —
import pyrerand everyexisting symbol are unchanged for callers.
```python
import pyrer
class Pep440Orderer(pyrer.PackageOrderer):
name = "pep440"
def order(self, family, versions):
return sorted(versions, key=_pep440_key, reverse=True)
pyrer.register_orderer(Pep440Orderer)
result = pyrer.solve(requests, packages, package_orderer="pep440")
```
package_ordereraccepts a registered name, aPackageOrdererinstance, orNone(default). An orderer is a preference function — never changeswhether a solve succeeds. Misbehaving orderers are handled defensively;
a raising
order()→status="error", no exception escapespyrer.solve.Rust core
The one solver-visible version-order site,
PackageVariantSlice::sort_versions(),now keys on a per-version
order_rankinstead ofversion.cmpdescending.order_rankis computed once per family inPackageVariantList::new— theno-orderer branch reproduces version-descending bit-identically. New
FamilyOrderercallback type,SolverContext.package_orderfield,Solver::new_with_options5th param.Verification
cargo test --libpytest tests/cargo clippytype_complexitywarnings in #92 test code)maturin developTest plan
build_rank_map+ orderer-driven solvesPep440Ordererto apyrer.PackageOrderersubclass and confirm resolves match Fortiche's rez viascripts/compare_resolves.pyFiles
crates/rer-resolver/—FamilyOrderertype,SolverContextfield + manualDebug+ builder,order_rank+build_rank_map+sort_versions,new_with_options5th param, 9 unit tests.crates/rer-python/—[lib] name = "_native",#[pymodule] _native,make_orderer,package_orderkwarg; newpython/pyrer/{__init__.py,orderer.py};pyproject.tomlmaturin mixed layout.tests/test_rich_api.py— 10 new tests.docs/,CHANGELOG.md,.gitignore.🤖 Generated with Claude Code