Skip to content

Draft: feat: package orderers as configurable plugins with config-arg-passing - #2165

Open
maxnbk wants to merge 7 commits into
AcademySoftwareFoundation:mainfrom
maxnbk:feat/package-orderers-as-configurable-plugins
Open

Draft: feat: package orderers as configurable plugins with config-arg-passing#2165
maxnbk wants to merge 7 commits into
AcademySoftwareFoundation:mainfrom
maxnbk:feat/package-orderers-as-configurable-plugins

Conversation

@maxnbk

@maxnbk maxnbk commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

** WIP, DO NOT REVIEW YET :) **

Summary

Rebases and fixes #1787 by @cfxegbert
Moves the 5 built-in package-orderers from src/rez/package_order.py to src/rezplugins/package_order/ as proper rez plugins.
Adds runtime configuration support via REZ_PACKAGE_ORDERERS_JSON and cache invalidation, creating a platform for PRs #1706 + #1709 to land in a usable manner with minimal changes.

What changed (on top of the PR #1787 rebase+fixing)

  1. Moves PackageOrderers into plugins.
  2. Registers PackageOrderPluginType in plugin_managers.py
  3. Replaces refactor: move package_order to rezplugin system #1787 's function-based backward-compat shims with PEP-562 __getattr__ lazy aliases, so isinstance, from_pod, type(x) is checks all work against the imported class names, while avoiding a circular import that module-level aliases would cause (plugin files import from package_order.py, which would call _find_orderer -> plugin system at load time)
  4. Keeps register_orderer() and the _orderers dict as a means to subclass/apply orderers programmatically.
  5. ruff-compatible reformatting of touched files ends up affecting many lines of code generically. It may be easiest to review commit by commit for this reason.

Runtime configuration of Package Orderers

  1. Documents that REZ_PACKAGE_ORDERERS_JSON works end-to-end using the existing _JSON env-var configuration pathway that then feeds into config.package_orderers -> PackageOrderList.singleton -> from_pod()
  2. Adds PackageOrderList.clear_singleton_cache() classmethod to invalidate the cached singleton, so runtime config changes can take effect without restarting, or API-based-usage of the feature can be correctly managed.

Documentation

  1. Adds REZ_PACKAGE_ORDERERS_JSON usage section to docs/source/package_orderers.rst
  2. Updates custom orderers section to recommend the plugin approach over register_orderer()

Design Notes

  1. The original plan was SortedOrder = _find_orderer('sorted') at module level, but this causes a circular import (package_order.py -> _find_orderer() -> plugin system -> rezplugins/package_order/sorted.py -> from rez.package_order import PackageOrder -> still importing). The __getattr__ approach defers resolution to the first attribute access, breaking the cycle.
  2. The locked config used by the test framework blocks env-var reads, so the runtime config tests simulate the REZ_PACKAGE_ORDERERS_JSON path by parsing JSON and using config.override() which is what the _JSON path does internally.
  3. Needed a lazy class resolution to survive test_plugin_manager's plugin manager reset

Testing

  1. 46 orderer tests + 2 completion tests
  2. Pre-flight tests were added before the refactor to catch potential regressions: from_pod round-trip, isinstance against imported classes, get_orderer default fallback
  3. New tests: plugin loading of all five built-in orderers, to_pod / from_pod round-trip through the plugin system, legacy register_orderer fallback, runtime config via config.override (simulating the _JSON path), cache invalidation, and a test-only kwarg-orderer that validates the pattern used in Add a package orderer that matches Python's version specifier spec (PEP440) #1706 / Added CustomPackageOrder #1709 (constructor kwargs configured at runtime).

Credit

Original refactor by @cfxegbert . Rebased on to current main, conflicts resolved, backward-compat shims fixed, and runtime configuration support added by me.

Follow-on work (not here)

  1. Possibly perform an API-breaking change from usage of PackageOrder class names (and other namings/imports/etc) to PackageOrderer for making-more-sense?
  2. PRs Add a package orderer that matches Python's version specifier spec (PEP440) #1706 and Added CustomPackageOrder #1709 could land as-is or could be migrated into rezplugins to join the rest.

Disclosure: This PR was assisted by GLM-5.2 for docstrings, documentation, and test generation.

maxnbk and others added 4 commits July 21, 2026 03:36
Add tests for:
- from_pod module-level function round-trip (the code path the plugin
  refactor will change)
- isinstance checks against imported class names (will catch function-shim
  breakage)
- get_orderer default fallback to SortedOrder(descending=True)

Signed-off-by: Stephen Mackenzie <maxnbk@users.noreply.github.com>
…ySoftwareFoundation#1787)

Move the five built-in package orderers (NullPackageOrder, SortedOrder,
PerFamilyOrder, VersionSplitPackageOrder, TimestampPackageOrder) from
src/rez/package_order.py into src/rezplugins/package_order/ as proper
rez plugins.

Register PackageOrderPluginType in plugin_managers.py.

Replace PR AcademySoftwareFoundation#1787's function shims with lazy module-level __getattr__
aliases (PEP 562) so isinstance, from_pod, and type(x) is checks all
work, while avoiding circular imports at module load time.

Keep _orderers dict and register_orderer() as legacy fallback, marked
with deprecation comments.

Original work by Robert Minsk (PR AcademySoftwareFoundation#1787). Rebased, conflicts resolved,
and fixed by Stephen Mackenzie.

Signed-off-by: Robert Minsk <robertminsk@yahoo.com>
Signed-off-by: Stephen Mackenzie <maxnbk@users.noreply.github.com>
Add tests for:
- Plugin system loads all five built-in orderers
- to_pod/from_pod round-trip through the plugin system
- Legacy register_orderer fallback
- REZ_PACKAGE_ORDERERS_JSON end-to-end configuration (simulated via
  config.override since the test framework uses a locked config)
- Cache invalidation for config changes
- Test-only kwarg-orderer validates the AcademySoftwareFoundation#1706/AcademySoftwareFoundation#1709 pattern

Signed-off-by: Stephen Mackenzie <maxnbk@users.noreply.github.com>
Add documentation for:
- REZ_PACKAGE_ORDERERS_JSON environment variable configuration
- Plugin-based custom orderer approach (preferred over register_orderer)
- Cache invalidation for runtime config changes

Signed-off-by: Stephen Mackenzie <maxnbk@users.noreply.github.com>
@maxnbk
maxnbk requested a review from a team as a code owner July 21, 2026 20:58
@maxnbk
maxnbk force-pushed the feat/package-orderers-as-configurable-plugins branch from 6022842 to 1951153 Compare July 22, 2026 03:38
maxnbk added 2 commits July 22, 2026 04:03
Update plugin files to match main's post-AcademySoftwareFoundation#1761 code:
- Add type annotations (Version, SupportsLessThan, Package, etc.)
- Add missing imports (each plugin now self-contained)
- Use 'is' instead of '==' for type comparisons (no E721)
- Fix E203 whitespace before ':' in soft_timestamp.py slice
- Match __eq__, to_pod, from_pod, __init__ signatures from main

Signed-off-by: Stephen Mackenzie <maxnbk@users.noreply.github.com>
…r resets

test_plugin_manager resets the plugin manager between test runs, which
deletes rezplugins.* from sys.modules and re-registers all plugin types.
This creates new class objects for orderer plugins. Tests that imported
orderer classes at module level (from rez.package_order import SortedOrder)
were bound to stale class objects, causing isinstance and __eq__ failures
when the full test suite ran in CI.

Fix: resolve orderer classes in setUp() via _find_orderer() so each test
method gets the current class objects.

Signed-off-by: Stephen Mackenzie <maxnbk@users.noreply.github.com>
@maxnbk
maxnbk force-pushed the feat/package-orderers-as-configurable-plugins branch from 1951153 to 39a2d37 Compare July 22, 2026 04:09
@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.54930% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.49%. Comparing base (3a50d1b) to head (b80ba21).

Files with missing lines Patch % Lines
src/rez/plugin_managers.py 56.66% 12 Missing and 1 partial ⚠️
src/rezplugins/package_order/soft_timestamp.py 95.65% 1 Missing and 2 partials ⚠️
src/rezplugins/package_order/no_order.py 90.90% 1 Missing and 1 partial ⚠️
src/rezplugins/package_order/per_family.py 97.10% 1 Missing and 1 partial ⚠️
src/rezplugins/package_order/sorted.py 92.85% 1 Missing and 1 partial ⚠️
src/rezplugins/package_order/version_split.py 92.30% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2165      +/-   ##
==========================================
+ Coverage   61.29%   61.49%   +0.19%     
==========================================
  Files         164      170       +6     
  Lines       20568    20639      +71     
  Branches     3575     3581       +6     
==========================================
+ Hits        12607    12691      +84     
+ Misses       7089     7075      -14     
- Partials      872      873       +1     

☔ View full report in Codecov by Harness.
📢 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.

…methods

Covers: sort_key with VersionRange/None/invalid types, FallbackComparable
fallback path, _find_orderer with unknown name, __getattr__ AttributeError,
register_orderer valid/invalid, PackageOrderList dirty-flag methods,
to_pod/from_pod round-trip, PerFamilyOrder sort_key_implementation with
default_order fallback and RuntimeError path.

Raises orderer test coverage from 85% to 97%.

Signed-off-by: Stephen Mackenzie <maxnbk@users.noreply.github.com>

@JeanChristopheMorinPerso JeanChristopheMorinPerso left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm generally good with this but would like to see some changes before we merge this. I think it's going in the right direction. Note that this review was a manual review from me with no LLM assistance.

Comment thread src/rez/package_order.py
packages=data.get("packages"),
)
if name in _LEGACY_ORDERER_NAMES:
return _find_orderer(_LEGACY_ORDERER_NAMES[name])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should raise a deprecation warning and pave the road for removing this fallback in a few releases. We can use rez.deprecations.warn.

"""
test completions
"""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would get rid of all the formatting changes if possible. We can make the formatting changes separately in another PR.

Comment thread src/rez/package_order.py

try:
return plugin_manager.get_plugin_class("package_order", name)
except RezPluginError:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth introducing a new exception class that subclasses RezPluginError to be more specific about what the error is, like RezPluginNotFound or something like that? Doing this wouldn't break user and it would improve our plugin system just a little bit.

Comment thread src/rez/package_order.py
Use this when runtime configuration (e.g. REZ_PACKAGE_ORDERERS_JSON)
has changed and you want the new config to take effect. Note that
the config-level cache must also be cleared via
``config._uncache("package_orderers")``.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

config._uncache("package_orderers")

This makes me feel uneasy as it's a private method. Also, why is this method needed? Are we doing something similar in other places and this just keeps things consistent? API users have access to rez.config.config.copy/override/remove_override to change the condig at runtime...

Comment thread src/rez/package_order.py
if orderer is None:
# default ordering is version descending
orderer = SortedOrder(descending=True)
sorted_order = _find_orderer("sorted")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
sorted_order = _find_orderer("sorted")
sorted_order = _find_orderer("SortedOrder")

to avoid relying on the module getattr.

Comment thread src/rez/package_order.py
# Orderers registered at runtime via register_orderer(). This is the API-based
# registration pathway, used as fallback by _find_orderer when an orderer is
# not found in the plugin system.
_orderers = {}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already defined at line 196.

Comment thread src/rez/package_order.py

returns:
bool: True if successfully registered, else False.
"""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add a deprecation warning to nudge users towards the plugins.

type_name = "command"


class PackageOrderPluginType(RezPluginType):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to update the plugin docs with this new plugin type: https://github.com/AcademySoftwareFoundation/rez/blob/main/docs/source/plugins.rst?plain=1#L14

Comment thread src/rez/package_order.py

This is the API-based registration pathway, useful for dynamic or
programmatic orderer registration. For persistent, facility-wide
orderers, prefer the plugin system (rezplugins/package_order/).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
orderers, prefer the plugin system (rezplugins/package_order/).
orderers, it is preferred to use :doc:`plugins <plugins>`.


Package orderers can be configured in the ``rezconfig.py`` via the :data:`package_orderers` setting.

Environment Variable Configuration

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be better if we document all the JSON environment variables. It feels weird that we would document this one specifically but not the others. Also, does the note about changing the config at runtime applicable to all settings? If so, it would be preferable to have this documented in the config docs.

@JeanChristopheMorinPerso JeanChristopheMorinPerso Aug 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a PR that adds documentation for JSON env vars.

@JeanChristopheMorinPerso

Copy link
Copy Markdown
Member

Oh, I'm also wondering if we should deprecate config.package_orderers in favor of configuring them through the plugins?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants