Repository navigation
Guard the process's warning filters against the model - #2379
Merged
Merged
Conversation
Until #1846, three LHA variable modules called warnings.filterwarnings("ignore") at import, and bi_household_phaseout did so in its formula. policyengine-core runs every variable file again on each CountryTaxBenefitSystem build, so building the model silenced every warning in the process, including inside pytest.warns. #1846 removed the filters; nothing stopped them coming back. - Add test_warning_filters.py. It checks that building a system or a Simulation and calculating its variables leave warnings.filters and numpy's error state unchanged. It checks that a UserWarning raised afterwards reaches pytest.warns and a fresh process under default filters. An AST scan fails on any unscoped filterwarnings, simplefilter, resetwarnings, warnings.filters use or np.seterr in the package. It also checks that the divisions the filters hid raise no RuntimeWarning, including at a zero denominator. - Drop the dated_reforms._warn logging workaround (#2305), whose stated reason was these filters, and assert ReformPeriodWarning with pytest.warns instead of caplog. - Remove the unused warnings imports left in bi_individual_phaseout and dynamics/participation. Fixes #2377 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n test Address the independent review of #2379: - The scanner follows "import warnings as w" and names imported from warnings or numpy under another name, flags only writes to warnings.filters (not reads), and treats a class body and a function's decorators and defaults as running inside a catch_warnings() block, and a generator expression's body as running later. - The division test runs under np.errstate(all="warn"), so a 0/0 cannot pass silently under an "ignore" error state left by earlier code. - The changelog fragment no longer says importing leaves the filters unchanged: dependencies add targeted filters at import. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
marked this pull request as ready for review
October 12, 2026 13:50
Collaborator
Author
|
Hand-off to the UK hub
|
github-merge-queue
Bot
removed this pull request from the merge queue due to no response for status checks
Oct 12, 2026
Collaborator
Author
|
Merged by the UK hub (audit). Merged with a merge commit at the tested head
|
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.
Fixes #2377
Reproducer: policyengine_uk/tests/test_warning_filters.py::test_a_warning_raised_after_simulating_reaches_pytest_warns
axiom: n/a: warning handling
What was wrong
Until 12 October,
LHA_allowed_bedrooms.py,LHA_cap.pyandLHA_eligible.pycalledwarnings.filterwarnings("ignore")at import, andbi_household_phaseout's formula called it on every run. policyengine-core runs every variable file again each time aCountryTaxBenefitSystemis built (add_variables_from_file→spec.loader.exec_module). So each build of the model put a global "ignore" at the front ofwarnings.filters. That silenced every later warning in the process, including inside apytest.warnsblock entered before the build. On 11 October aReformPeriodWarning(#2305) never reached the caller.#1846 (merged 03:07 UTC on 12 October, commit 00c07d6) removed the four filters and replaced the divisions they hid with
np.divide(..., where=). Its fresh-interpreter test coversimport policyengine_uk. This PR adds the guard that keeps the filters from coming back, and removes #2305's workaround for them.Where the filters came from (git history)
LHA.pytime_shift_datasetassigned columns on filtered pandas slices (df.year = ...,df.weekly_rent = ...), which raisesSettingWithCopyWarning. #1139 (2025-06-06) splitLHA.pyinto one file per variable and copied the header into all of them, including these three, which had no pandas code. 43ef96f (2026-10-01) dropped it fromBRMA_LHA_rate,LHA_categoryanduc_LHA_cap.bi_household_phaseout, in the formulaRuntimeWarningfromwhere(household_bi > 0, capped_deduction / household_bi, 0), which evaluates both branches.None of these filters is needed now. The guarded divisions raise no warning at all, which is better than a scoped
catch_warnings(), and no LHA module uses pandas.Changes
policyengine_uk/tests/test_warning_filters.py(new) holds the regression tests below.policyengine_uk/utils/dated_reforms.py:_warnno longer logs each message as well as warning ("some variable modules switch warnings off when loaded"). That reason no longer holds, and callers saw each message twice. The unusedloggingimport andloggergo with it.policyengine_uk/tests/test_dated_reform_routes.py: the twoReformPeriodWarningtests now assert withpytest.warns(ReformPeriodWarning)instead ofcaplog. This is the 11 October case end to end: the warning is raised after the simulation loads the variable modules. The no-warning case usessimplefilter("error", ReformPeriodWarning).import warningsleft inbi_individual_phaseout.pyanddynamics/participation.py.Invariants (tested)
CountryTaxBenefitSystemor aSimulation(situation=...)and calculating its variables leaveswarnings.filtersand numpy's error state as they were. Importing the package leaves numpy's error state alone and adds no filter that ignores a built-in warning category everywhere.UserWarningraised after building reaches the caller: inside apytest.warnsblock entered before the build, and in a fresh interpreter under Python's default filters.warnings.catch_warnings(). None changes numpy's error state withnp.seterr;np.errstate()is the scoped form. An AST scan of all package modules (tests excluded) checks this. A filter inside a formula only runs when that formula runs, so building the model alone cannot catch one. The scanner followsimport warnings as wand names imported fromwarningsor numpy under another name. It flags writes towarnings.filters(assignment, slice assignment,del, list methods), not reads. A class body, decorators and defaults run inside acatch_warnings()block, so they count as scoped. The body of a function or generator expression defined there runs later, so it does not. It has 24 positive and negative cases of its own.RuntimeWarning, including at a zero denominator. The baseline has no basic income, so the phase-out divides by zero there, and the test checks that it does. The test runs undernp.errstate(all="warn"), so an "ignore" error state left by earlier code cannot let an unguarded 0/0 pass silently.The in-process checks start from an "always" filter and
np.errstate(all="warn"). Without the first, a filter identical to the one at the front would leave the list unchanged. Without the second, the check would inherit numpy state left by an earlier test, because pytest does not reset it. The fresh-process check snapshots beforeimport policyengine_uk, because the import itself builds a system.Mutation check
I put each bug back on this branch at 68595fc, ran the 34 new and converted tests (including the scanner's own cases), then reverted. The script is
~/reviews/uk-warning-filters-2377/mutate.sh, and the output is inmutation-results-r2.txtin the same folder.warnings.filterwarnings("ignore")at the top ofLHA_cap.py(the pre-#1846 state)pytest.warns, fresh process, bothReformPeriodWarningtestsbi_household_phaseout's formulapytest.warns, fresh process. The build-only test passes, because the formula does not run during a build; the scan exists for this case.capped_deduction / household_bi, with no filternp.seterr(all="ignore")at the top ofLHA_cap.pywarnings.filterwarnings("ignore", category=RuntimeWarning)at the top ofLHA_cap.pynumpy.seterr(all="ignore")run before the tests (intests/conftest.py, which the scan skips)np.errstatepin, the review showed this test passed despite the unguarded 0/0.from warnings import filterwarnings as fwwithfw("ignore")insidebi_household_phaseout's formulapytest.warns, fresh processClean branch: 34 passed.
Tests run
make format: ruff format and ruff check pass.pytest -n 4ontest_warning_filters.py,test_dated_reform_routes.py,test_dataset_fiscal_year.py,test_household_benefits_properties.py,test_participation_earnings_quintile.py,test_participation_hours_units.pyandtest_salary_sacrifice_needs_pay_properties.py: 120 passed.policyengine-core teston the basic income YAML tests (reforms/parametric/basic_income,baseline/contrib/ubi_center): 10 passed.Reproducer:line, import aliases getting past the scanner, numpy state in the division test, and the changelog wording. All four are addressed in 68595fc. The report is in~/reviews/uk-warning-filters-2377/review-sol-c7a9da756.md.~/reviews/uk-warning-filters-2377/review-sol-68595fc14.md. It raised three low, non-blocking points, none of which any current package module hits:f = warnings.filters; f.insert(...);filtersfromwarningseven after that name is rebound to a list;Cost: the new module takes about 25 s on 4 workers, of which about 8.5 s is the fresh interpreter (peak RSS 360 MB).
Notes
dated_reforms.pyand appends tests totest_dated_reform_routes.py. Its changed lines don't overlap or touch this PR's. The review rangit merge-treein both orders against Roll back failed override batches and keep structural-reform nodes across rebuilds #2375's head d594634 and found no conflict.🤖 Generated with Claude Code