repomatic.github.matrix module¶
GitHub Actions job-matrix model: variations, includes, excludes, and their
expansion into the JSON payload workflow strategy.matrix keys consume.
- repomatic.github.matrix.JOB_STATE_KEY = 'state'¶
Job key carrying whether a cell runs as
stableorunstable.Matrix.pivot()reads it into each cell, absent a caller’s choice.
- repomatic.github.matrix.OS_AXIS = 'os'¶
Job key naming the runner image, laid out as
Matrix.pivot()columns.
- repomatic.github.matrix.PYTHON_VERSION_AXIS = 'python-version'¶
Job key naming the interpreter version, laid out as
Matrix.pivot()rows.
- repomatic.github.matrix.PIVOT_CELL_SEPARATOR = ', '¶
Joins the distinct states
Matrix.pivot()finds at one intersection.Exposed so a caller rendering the grid can split a cell back into its states and decorate each one, instead of hard-coding the separator on its side and letting the two drift.
- repomatic.github.matrix.RESERVED_MATRIX_KEYWORDS = ('include', 'exclude')¶
Keys GitHub reserves inside a
strategy.matrixblock.Neither can name a variation axis, since both already mean something to the matrix expander.
Matrix._check_ids()rejects them.
- repomatic.github.matrix.stale_axis_values(entry, axes)[source]¶
Return the
entrykey/value pairs absent from the matrixaxes.A non-empty result means an
excludedirective can never match a combination: one of its keys is not a live axis, or its value is absent from that axis.Matrix.prune()drops such a directive silently, since GitHub rejects a matrix whose excludes name unknown keys; this is the predicate behind that decision, exposed so callers can also report the drift instead of only absorbing it (seeMetadata.stale_test_matrix_excludes).
- class repomatic.github.matrix.Matrix[source]¶
Bases:
objectA matrix as defined by GitHub’s actions workflows.
See GitHub official documentation on how-to implement variations of jobs in a workflow.
Note
Why matrices are pre-computed in the
metadatajobGitHub Actions matrix outputs are not cumulative — the last job in a matrix wins (community discussion). This makes a matrix-based job terminal in a dependency graph: no downstream job can depend on its aggregated outputs.
The workaround is a single preliminary
metadatajob that computes all matrices upfront. Downstream jobs depend on that job and consume the pre-built matrices, rather than computing them themselves.A matrix starts empty and is populated through its own methods, never through the constructor:
matrix()renders the result as an immutableFrozenDictfor serialization, and__getitem__()reads a single axis, but the object itself is not a mapping: it holds axes, includes and excludes as separate state.The implementation respects the order in which items were inserted. This provides a natural and visual sorting that should ease the inspection and debugging of large matrix.
- matrix(ignore_includes=False, ignore_excludes=False)[source]¶
Returns a copy of the matrix.
The special
includeandexcludesdirectives will be added by default. You can selectively ignore them by passing the corresponding boolean parameters.
- replace_variation_value(variation_id, old, new)[source]¶
Replace a single value within a variation axis.
The new value takes the position of the old value. If the new value already exists elsewhere in the axis, the duplicate is removed by
boltons.iterutils.unique().Silently skips if the axis does not exist or does not contain the old value, making the operation idempotent.
- Return type:
- remove_variation_value(variation_id, value)[source]¶
Remove a single value from a variation axis.
If the axis becomes empty after removal, it is deleted entirely.
Silently skips if the axis does not exist or does not contain the value, making the operation idempotent.
- Return type:
- add_includes(*new_includes)[source]¶
Add one or more
includespecial directives to the matrix.- Return type:
- add_excludes(*new_excludes)[source]¶
Add one or more
excludespecial directives to the matrix.- Return type:
- prune()[source]¶
Remove no-op exclude directives and log about them.
An exclude is a no-op when it references a key that is not a variation axis at all, or when the key exists but the value is not present in that axis. Either way the exclude can never match any combination produced by
product(), and GitHub Actions rejects excludes that reference non-existent matrix keys.- Return type:
- all_variations(with_matrix=True, with_includes=False, with_excludes=False)[source]¶
Collect all variations encountered in the matrix.
Extra variations mentioned in the special
includeandexcludedirectives will be ignored by default.You can selectively expand or restrict the resulting inventory of variations by passing the corresponding
with_matrix,with_includesandwith_excludesboolean filter parameters.
- product(with_includes=False, with_excludes=False)[source]¶
Only returns the combinations of the base matrix by default.
You can optionally add any variation referenced in the
includeandexcludespecial directives.Respects the order of variations and their values.
- solve(strict=False)[source]¶
Expand the matrix to explicit jobs, applying
excludetheninclude.Reproduces GitHub’s documented matrix algorithm:
Build the cross-product of the base variations.
Drop every combination matching an
excludedirective. A directive matches when all of its keys equal the combination’s, so a partial directive removes a whole slice.Process
includedirectives in order. Each is merged into every product combination it does not conflict with (it conflicts when it would overwrite an original axis value). A directive merging into no combination is appended as a new standalone job.
Note
includedirectives augment combinations from the base cross-product only, never jobs created by an earlierinclude. An excluded combination is resurrected solely when anincludefully re-specifies it, so it merges into nothing and is appended: a partialincludethat augments surviving jobs does not bring excluded slices back. GitHub remains the authoritative expander, but this follows its documented rules so downstreamfull-includejob lists (whichmatrix()serializes verbatim) match what GitHub would run.
- pivot_counts(row_axis='python-version', col_axis='os', cell_key='state')[source]¶
Tally, per grid intersection, the jobs carrying each
cell_keyvalue.The grouping
pivot()renders, kept as counts rather than collapsed into a string. A caller cannot recover them from the rendered grid, where a cell holding five identical states and one holding a single job read alike, which is the whole reason a matrix varying on a third axis needs this.- Parameters:
- Return type:
- Returns:
A
(row_value, col_value)toCountermapping, in first-seen job order, each counter keyed in first-seen order too. An intersection no job occupies is absent from the mapping rather than present with an empty counter.
- pivot(row_axis='python-version', col_axis='os', cell_key='state', missing='—')[source]¶
Pivot the solved matrix into a 2D grid keyed by two axes.
Expands the matrix with
solve(), then arranges the resulting jobs into a grid: one row per distinctrow_axisvalue, one column per distinctcol_axisvalue. Each cell holds the job’scell_keyvalue at that intersection (itsstate, by default), ormissingwhen no job occupies it (an excluded combination).- Parameters:
- Return type:
- Returns:
A
(col_values, rows)pair.col_valuesis the ordered tuple of column values (distinctcol_axisvalues). Each entry inrowsis(row_value, cell, …), with one cell percol_valuesentry.
Note
Axis values keep first-seen order in the solved job stream. That matches the declared axis order for a base cross-product matrix, and the emitted job order for a flattened
full-includematrix.When several jobs share one (row, col) intersection (a matrix carrying extra axes, such as a
click-versionvariation), their distinctcell_keyvalues are joined withPIVOT_CELL_SEPARATOR. A matrix with only theosandpython-versionaxes has exactly one job per cell.