Skip to content

[BUGFIX] Stop silencing warnings for users with no logging configured - #12070

Open
aleks-drozy wants to merge 1 commit into
fivetran:developfrom
aleks-drozy:fix/logging-capturewarnings-12067
Open

[BUGFIX] Stop silencing warnings for users with no logging configured#12070
aleks-drozy wants to merge 1 commit into
fivetran:developfrom
aleks-drozy:fix/logging-capturewarnings-12067

Conversation

@aleks-drozy

Copy link
Copy Markdown

Root cause

Five modules call logging.captureWarnings(True) unconditionally at import time (module top-level):

  • great_expectations/validator/validator.py
  • great_expectations/validator/validation_graph.py
  • great_expectations/validator/metrics_calculator.py
  • great_expectations/core/batch_manager.py
  • great_expectations/expectations/core/expect_column_kl_divergence_to_be_less_than.py

logging.captureWarnings(True) is process-global — it monkeypatches warnings.showwarning for the entire interpreter. Because import great_expectations transitively imports all five modules, simply importing the library flips this behavior for the whole process, including code that never touches great_expectations.

The silencing itself comes from a well-known CPython logging gotcha, not anything great_expectations codes explicitly. CPython's replacement showwarning does roughly:

logger = getLogger("py.warnings")
if not logger.handlers:
    logger.addHandler(NullHandler())
logger.warning("%s", s)

So the first warning emitted after captureWarnings(True) causes Python to attach a NullHandler to the py.warnings logger if nothing else is configured. That satisfies Logger.callHandlers's "was a handler found anywhere in the chain?" check, so the record is silently consumed and logging.lastResort (which would otherwise print unhandled WARNING+ records to stderr) never fires. Users who already configure logging themselves aren't affected, since their own handler intercepts the record — only users with zero logging configuration lose their warnings.

Fix

Removed the five module-level logging.captureWarnings(True) calls. The logger = logging.getLogger(__name__) lines directly above each were left intact since they're independently used elsewhere in those modules. No replacement logic is needed — removing the calls restores Python's default warnings display behavior. Applications that want warnings routed through logging can still opt in themselves by calling logging.captureWarnings(True).

Testing

Added tests/test_warnings_not_silenced.py, which spawns a subprocess running import great_expectations; warnings.warn(...) and asserts the warning appears on stderr. A subprocess is required (rather than pytest.warns/catch_warnings) because pytest's own warning-capture fixtures install their own showwarning hook and would mask the regression — this mirrors the reproduction steps from the issue.

  • Confirmed the test fails on develop (stderr is empty) and passes after the fix (stderr contains the warning).
  • Ran the touched-area test slice with no regressions: tests/validator/test_validation_graph.py, tests/validator/test_metrics_calculator.py, tests/validator/test_validator.py, tests/validator/test_v1_validator.py, and the KL-divergence expectation integration tests (tests/integration/data_sources_and_expectations/expectations/test_expect_column_kl_divergence_to_be_less_than.py, pandas backend) — all passing.
  • ruff check clean on all changed files.

Fixes #12067

Five modules (validator.py, validation_graph.py, metrics_calculator.py,
batch_manager.py, expect_column_kl_divergence_to_be_less_than.py) called
logging.captureWarnings(True) at import time. This is process-global: it
redirects warnings.showwarning into the logging module for the entire
interpreter. Since CPython's captureWarnings machinery attaches a
NullHandler to the "py.warnings" logger on first use when no handler is
configured, any user importing great_expectations with no logging setup
had warnings.warn(...) silently swallowed everywhere in their process,
not just within great_expectations code.

Remove the five captureWarnings(True) calls; the module-level `logger =
logging.getLogger(__name__)` lines are untouched since they're used
independently. Applications that want warnings routed through logging
can still opt in themselves via logging.captureWarnings(True).

Fixes fivetran#12067
@netlify

netlify Bot commented Aug 16, 2026

Copy link
Copy Markdown

👷 Deploy request for niobium-lead-7998 pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit 01983db

@github-actions github-actions Bot added the cla-not-signed https://github.com/fivetran/great_expectations/blob/develop/CLA.md label Aug 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your contribution! Before we can merge this pull request, every committer needs to have signed our Contributor License Agreement (CLA).

We could not find a signed CLA for: @aleks-drozy. Please sign the Individual Contributor License Agreement, or the Software Grant and Corporate Contributor License Agreement if you are contributing on behalf of your employer (see CLA.md for details).

Once resolved, comment @cla-bot check on this pull request to re-run the check.

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

Labels

cla-not-signed https://github.com/fivetran/great_expectations/blob/develop/CLA.md

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Importing great_expectations silences all warnings for users with no logging configured

1 participant