🐛 An invalid predicates expression warns instead of ending the build - #1791
Merged
Conversation
A `predicates` match expression on a `needs_fields` or `needs_links` default that cannot be evaluated -- one naming `section_name` or `content`, say, neither of which a predicate has access to -- raised `NeedsInvalidFilter` out of `add_need` and ended the whole build with a traceback and a "please report this to the developers" banner. No need was created, and no other warning in the project was ever reported. Such an expression is now reported as a `needs.config` warning, located at a need it was evaluated against and naming the field, the expression and the underlying error, and is then skipped: the remaining predicates are still evaluated, and if none of them matches then the plain `default` applies, exactly as it does for a predicate that simply did not match. A statically malformed `predicates` value has always been reported this way; only the expression itself was left to end the build. The warning is deduplicated on its full message, so a single configuration mistake is reported once rather than once per need; an expression that fails only for some needs, or for different reasons on different needs, is reported once per distinct error.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1791 +/- ##
==========================================
+ Coverage 86.87% 91.05% +4.17%
==========================================
Files 56 77 +21
Lines 6532 11688 +5156
==========================================
+ Hits 5675 10643 +4968
- Misses 857 1045 +188
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
ubmarco
approved these changes
Aug 26, 2026
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.
A
predicatesmatch expression on aneeds_fieldsorneeds_linksdefault thatcannot be evaluated ends the entire build with a traceback, rather than being
reported like every other bad expression in the project.
What happens today
No need is created, no other warning in the project is ever reported, nothing names
the document the expression failed on, and the user is pointed at Sphinx's issue
tracker for what is a typo in their own
conf.py.needs_linkspredicates fail thesame way (
"'body' in content"→name 'content' is not defined).The trap is easy to fall into, because the names most likely to be reached for —
section_name,sections,content,lineno— all work in ordinary filters, andnone of them is available to a predicate.
What this changes
NeedsInvalidFilteris caught where the predicate is applied, in_get_field_default/_get_links_default, and reported instead:The predicate is then skipped: the remaining predicates are still evaluated, and if
none of them matches, the plain
defaultapplies — exactly as it does for apredicate that simply did not match. The need is created and the build completes.
apply_default_predicatekeeps its documented:raises NeedsInvalidFilter:contract; the catch is at the call sites, which are the narrowest seam that has a
need location to report against.
The warning subtype
needs.config, reusing an existing subtype rather than adding one:predicatesvalue is already reported asneeds_fields['x']['predicates'] value is incorrect: ... [needs.config]by_set_predicates_on_field. This is the runtime half of the same mistake on thesame configuration key, and now reads as its sibling;
needs_flow_enginevalue — a badconfiguration value, warned as
needs.configat the use site withonce=True,falling back to the sane default.
It is deduplicated
once=Trueon its full message: a single configuration mistake —like the name typo above, which fails identically for every need — is reported once
rather than once per need. An expression that fails only for some needs, or for
different reasons on different needs (say
int(status) > 1across varying statusvalues), is reported once per distinct error, which keeps each real failure visible.
One recorded limitation: the message names the canonical
needs_fields/needs_linkskey, so a predicate configured through the deprecatedneeds_global_options, aneeds_extra_linksentry, oradd_field(...)is labelledwith the canonical key rather than the one the project wrote. The field name in the
message always identifies the culprit, and threading true origins through the schema
is out of scope for a single-purpose fix.
Tests
test_invalid_predicate_defaultintests/test_field_defaults.py, one buildcovering: an invalid predicate on a field default (warning subtype, location and
text asserted; the plain
defaultapplies), the same on a link default, a fieldwhose plain
defaultis absent (stays unset — current semantics pinned, notchanged), a valid predicate that still applies, and a valid predicate after a
broken one, which still wins over the plain
default.The test was written first and fails on
masterby killing the build out ofapp.build(), which is the defect itself. The two existing tests in that module,including their verbatim warning lists, are unchanged.
Related
Same class as #1537 — one bad value costing far more than itself — though a
different cause; this is not a duplicate of it.
The
predicatesdocumentation did not say what happens to an expression thatcannot be evaluated, so one sentence has been added to the "Default values"
subsections of both
needs_fieldsandneeds_links.