Skip to content

feat(facts, operations): add OpenWrt packages, move opkg underneath openwrt and add packages and update operations#1847

Open
morrison12 wants to merge 26 commits into
pyinfra-dev:3.xfrom
morrison12:feat-openwrt-gen-pkg
Open

feat(facts, operations): add OpenWrt packages, move opkg underneath openwrt and add packages and update operations#1847
morrison12 wants to merge 26 commits into
pyinfra-dev:3.xfrom
morrison12:feat-openwrt-gen-pkg

Conversation

@morrison12

@morrison12 morrison12 commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Add OpenWrt facts and operations packages, move the existing opkg items there and add packages and update operations that select apk vs. opkg based on the running OpenWrt release.

The rationale for the OpenWrt package is that there are three OpenWrt -specific items (opkg, procd, uci) needed which don't need to be at the top-level. opkg exists today (even if it is being supplanted by apk), procd support will arrive soon and there are a couple of people working on uci support.

The expectation is that the top-level opkg will be removed after a suitable interval; the interval can probably be reasonably be short as the OpenWrt change from opkg to ask in their Release 25.12 means most users will switch to the release/package-management independent openwrt.packages once it is available.

Specifically changes here are:

  1. add OpenWrt facts and operations packages:
  • add the directories themselves
  • adopt and then modify the deferred import mechanism used for operations/freebsd to allow for the equivalent of import .sub-package.something as something which is useful to avoiding unnecessarily levels in the namespace.
  • modify the operations/freebsd implementation to be consistent with the openwrt implementations
  • change test_facts.py to allow for packages (vs. files) by making the same change as was made for test_operations.py
  1. move the existing opkg facts and operations to openwrt
  • move the implementation files to the relevant directory
  • rename the test folders
  1. replace the now-moved opkg facts and operations with deprecated "forwarders"
  • provides backwards compatibility
  • add support for deprecated facts (both long and short) using same approach as taken for operations
  • add limit of one warning per pyinfra run per deprecated fact as some facts are called a large number of times
  • add a warning to the top of the module-level doc string about the deprecation of the top-level opkg for both facts and operations
  • copy the relevant tests from openwrt.opkg
  1. create an OpenWrtHasFeature fact, a general version of check needed for 6
  • currently decides based on OpenWrt release but does not need to be restricted to that
  • a few lines of code can be removed when test_facts.py and pyinfra_cli support enum parameters
  1. add release and package-manager independent packages and update operations for OpenWrt
  • useOpenWrtHasFeature to decide whether to use apk or opkg
  • no corresponding Packages fact: is it is only consulted prior to doing something which is already covered for the packages and update cases
  1. add facts and operations entries in pyinfra-metadata.toml for (1)
  2. fixed documentation generation to work for facts and operations in packages and not just files
  1. changed sorting order of operations in scripts/generation_operations_docs.py to match sorting order used for imports; this affects not just the order on the page but also the order in the sidebar

A couple of questions related to the deprecation at (3) above:
a) there is a warning in the documentation for the deprecations but is there a specific admonition for deprecation that should have been used ?
b) does the future release need to be specified now (the documentation currently doesn't specify one)

One note: at (5), i.e. the OpenWrtHasFeature fact, there is a core mechanism (ReleaseRange.contains) for which it wasn't clear where to put the (non-fixture) tests. I opted for tests/test_facts_other.py but am happy to relocate them if there is a preferred location for such tests.

  • Pull request is based on the default branch (3.x at this time)
  • Pull request includes tests for any new/updated operations/facts
  • Pull request includes documentation for any new/updated operations/facts
  • Tests pass (see scripts/dev-test.sh)
  • Type checking & code style passes (see scripts/dev-lint.sh)
  • Pull request title follows the
    conventional commits format

- make same change as was done for same reason for operations
…OpenWrt specific modules

- simplifies top-level name space when packages, procd and uci arrive

- move opkg under openwrt (will be backfilled with deprecated version that can be removed)
- takes a OpenWrtFeature enum instance and tells if the feature is supported by the release

- use to determine opkg vs. apk or DSA vs. switch or FW4 vs. FW3, etc
… choice

- use OpenWrtHasFeature to decide which package manager to use
…cated forwarder

- preserves backwards compatibility while allowing deprecation to be noted
… deprecated.

- same approach as operations: is_deprecated and, optionally, deprecated_for
- only emit one warning per distinct fact regardless of the number of invocations
…t.opkg

- preserve backwards compability while giving notice of deprecation.
…entries for openwrt to pyinfra-metadata

- required for them to show up in the documentation cards
…at use packages instead of single files

- deferred imports broke "docs for packages" fix
- change deferred import mechanism to allow equiv. of from .x import y
- used updated mechanism for both freebsed operations and openwrt facts and operations
- change deferred imports mechanism to dispose of module if only a single element imported
- above needed as otherwise importer bypasses getattr
- update fact and operation discovery to use table of deferred imports for packages
- move remove_dups into discovery code
- i.e. <something> will come before <and.this.thing> even though and sorts before something
@wowi42 wowi42 added new feature facts Issues with facts. operations Issues with operations. API API mode specific issues. labels Jun 22, 2026

@wowi42 wowi42 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict: NO-GO

Well-structured PR with good deprecation handling and broad test coverage. Three issues need fixing before merge.

Issues

1. Fixture bug: has_dsa_is_false_for_19_07.json tests the wrong feature

tests/facts/openwrt.features.OpenWrtHasFeature/has_dsa_is_false_for_19_07.json has "arg": ["has_dsa"] and "command": "echo has_dsa && ...", but the first line of output is "uses_apk" instead of "has_dsa". The test passes accidentally because both features return false for release 19.07, but the fixture is not testing what its name and args claim. First output line must match the echoed feature name: "has_dsa".

2. Missing fixture: openwrt.update (i.e. openwrt.packages.update) has no tests

The update function in src/pyinfra/operations/openwrt/packages.py is a @operation that calls host.get_fact(OpenWrtHasFeature, ...). There is no tests/operations/openwrt.packages.update/ directory. Create at least two fixtures covering the apk and opkg branches.

3. Drive-by refactor of freebsd/__init__.py — out of scope

src/pyinfra/operations/freebsd/__init__.py replaces the glob-based module auto-discovery with an explicit __ALL__ dict and a getattr that splits on dots for sub-module attribute access. This is unrelated to OpenWrt. The glob approach was more maintainable (drop a new .py under freebsd/ and it's auto-discovered); the explicit dict requires manual registration. If this change is intentional, split it into its own PR with a rationale.

Notes

  • Copy-paste comment errors in __getattr__ docstrings: src/pyinfra/operations/freebsd/__init__.py line 17, src/pyinfra/operations/openwrt/__init__.py line 19, and src/pyinfra/facts/openwrt/__init__.py line 19 all say "On-demand import of OpenWrt facts" but should reference the correct package (FreeBSD operations, OpenWrt operations, OpenWrt facts respectively).
  • openwrt.packages has packages: str | list[str] = "" default. Per AGENTS.md new operations should use None. The "" default passes through to apk.packages._inner() which treats "" as a package name rather than nooping. Minor since the operation is always called with explicit package lists, but worth aligning with None convention.

- correct first line of output to be has_dsa (not copy paste is_apk)
- change back to previous approach.
- use | None = None for both openwrt.packages and opkg.packages
- adjust tests accordingly
- rework opkg (older code)
- parametrize FactBase
- set output type to list[str]
- set return types for process
- src/pyinfra/facts/openwrt/opkg.py
- change from object to Any for __init__.py in openwrt
@morrison12

Copy link
Copy Markdown
Contributor Author

Verdict: NO-GO

Well-structured PR with good deprecation handling and broad test coverage. Three issues need fixing before merge.

Issues

1. Fixture bug: has_dsa_is_false_for_19_07.json tests the wrong feature

tests/facts/openwrt.features.OpenWrtHasFeature/has_dsa_is_false_for_19_07.json has "arg": ["has_dsa"] and "command": "echo has_dsa && ...", but the first line of output is "uses_apk" instead of "has_dsa". The test passes accidentally because both features return false for release 19.07, but the fixture is not testing what its name and args claim. First output line must match the echoed feature name: "has_dsa".

Fixed.

2. Missing fixture: openwrt.update (i.e. openwrt.packages.update) has no tests

The update function in src/pyinfra/operations/openwrt/packages.py is a @operation that calls host.get_fact(OpenWrtHasFeature, ...). There is no tests/operations/openwrt.packages.update/ directory. Create at least two fixtures covering the apk and opkg branches.

Indeed needed. Added.

3. Drive-by refactor of freebsd/__init__.py — out of scope

src/pyinfra/operations/freebsd/__init__.py replaces the glob-based module auto-discovery with an explicit __ALL__ dict and a getattr that splits on dots for sub-module attribute access. This is unrelated to OpenWrt. The glob approach was more maintainable (drop a new .py under freebsd/ and it's auto-discovered); the explicit dict requires manual registration. If this change is intentional, split it into its own PR with a rationale.

Removed.
I had changed freebsd/__init__.py in hopes of avoiding future duplicate package (e.g. the current freebsd.sysrc.sysrc); it does take some effort but IMHO that was a nudge to think about the API.
Will put a separate PR for that on my list.

Notes

  • Copy-paste comment errors in __getattr__ docstrings: src/pyinfra/operations/freebsd/__init__.py line 17, src/pyinfra/operations/openwrt/__init__.py line 19, and src/pyinfra/facts/openwrt/__init__.py line 19 all say "On-demand import of OpenWrt facts" but should reference the correct package (FreeBSD operations, OpenWrt operations, OpenWrt facts respectively).

Fixed.

  • openwrt.packages has packages: str | list[str] = "" default. Per AGENTS.md new operations should use None. The "" default passes through to apk.packages._inner() which treats "" as a package name rather than nooping. Minor since the operation is always called with explicit package lists, but worth aligning with None convention.

Fixed both openwrt.packages and opkg.packages (no backwards compatibility issue).
Not sure how many humans read AGENTS.md; we should look at adding some of what is desired (such changes, new fixtures in YAML, etc to CONTRIBUTING.md and/or the documents.

Also I converted the fixtures to YAML while I was here and added typing to openwrt.opkg (which predated typed pyinfra).

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

Labels

API API mode specific issues. facts Issues with facts. new feature operations Issues with operations.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants