Skip to content

fix: attribute save_to_* deprecation to caller - #1346

Open
davidberenstein1957 wants to merge 2 commits into
masterfrom
fix/deprecation-warning-stacklevel
Open

fix: attribute save_to_* deprecation to caller#1346
davidberenstein1957 wants to merge 2 commits into
masterfrom
fix/deprecation-warning-stacklevel

Conversation

@davidberenstein1957

@davidberenstein1957 davidberenstein1957 commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

What changed

  • codecarbon/emissions_tracker.py: the save_to_* DeprecationWarning now computes its stacklevel dynamically (_caller_stacklevel()) instead of hardcoding stacklevel=2, so it is attributed to the code that constructed the tracker.
  • codecarbon/cli/monitor.py: dropped save_to_logger=False, which was both the default and the only reason the CLI tripped its own deprecation check.
  • codecarbon/cli/main.py: monitor now passes an explicit output_methods= list built from the user's configuration, with OutputMethod.API added or removed according to --api / --no-api; detect now passes output_methods=[].

Why

_resolve_output_methods is called from BaseEmissionsTracker.__init__, so stacklevel=2 pointed at emissions_tracker.py itself. Python's default ignore::DeprecationWarning filter then dropped the warning for every caller that is not a top-level __main__ script — the users the deprecation was written for never saw it.

A fixed stacklevel=3 would not work either: OfflineEmissionsTracker.__init__ adds a frame, and its @suppress(Exception) decorator adds a contextlib frame on top of that. Walking the stack to the first frame outside the package handles all entry points, including @track_emissions.

Because the corrected attribution makes the warning visible, the CLI's own deprecated call sites had to move to output_methods= in the same change, otherwise every codecarbon monitor / codecarbon detect invocation would warn users about a choice the CLI made (and hard-fail under -W error::DeprecationWarning).

⚠️ Behaviour changes this drags along

Migrating the CLI off save_to_api= is not a like-for-like swap, because output_methods= and save_to_* do not compose the same way: when output_methods resolves to a non-empty value (from the constructor or from configuration), _resolve_output_methods derives all five save_to_* flags from it and returns early. So output_methods replaces, where save_to_api=True added. Two consequences, both now handled:

1. --api must stay additive. An earlier revision of this PR passed a hardcoded output_methods=[OutputMethod.CSV, OutputMethod.API]. That replaced the user's configuration outright: someone with save_to_prometheus = true (or output_methods = prometheus) in .codecarbon.config running codecarbon monitor --api would silently lose Prometheus and silently gain an emissions.csv they never asked for. That is not what save_to_api=True did, so it is now built from the configuration instead, via a small _configured_output_methods() helper that mirrors the tracker's own config-only resolution.

As a side effect this also fixes a pre-existing hole: on master, --api was silently ignored whenever output_methods was set in configuration, because the explicit save_to_api=True lost to the early return. It now works.

2. --no-api has to keep working. api defaults to True, so --no-api is the off switch. master passed save_to_api=False unconditionally, which forced the API off regardless of configuration. Passing the flag only when it is set — as an earlier revision of this PR did — would have made --no-api a no-op for anyone with save_to_api = true in their config file: plain codecarbon monitor would have started pushing to the API and there would have been no way to turn it off from the command line. monitor therefore always passes output_methods=, with API removed when --no-api.

The same applies to save_to_logger=False, dropped from codecarbon/cli/monitor.py: configuration now decides. Since monitor passes an explicit output_methods= list on the online path, that list is authoritative there; run_and_monitor under --offline no longer forces the logger off, which is the intended "configuration wins" direction.

Given that --api / --no-api now both map cleanly onto add/remove, a tri-state Optional[bool] ("unset = whatever the config says") is not proposed here: with api=True as the default, an unset third state would be unreachable from the command line without also flipping the default, which is a separate, user-visible CLI change. Happy to do it as a follow-up if you would rather codecarbon monitor default to the configuration.

How it was verified

  • New tests/test_emissions_tracker.py::test_deprecation_warning_points_at_caller asserts the recorded warning's filename is the test module for both EmissionsTracker and OfflineEmissionsTracker. It fails on master and passes here.
  • New tests/cli/test_monitor.py::test_run_and_monitor_does_not_pass_deprecated_flags asserts the CLI passes no save_to_* kwarg to the tracker. Also fails on master.
  • New parametrised tests/cli/test_cli_main.py::test_monitor_api_flag_adds_to_configured_output_methods pins the additive contract for a configured output_methods = prometheus, for save_to_file=false + save_to_logger=true, and for the already-contains-API no-duplicate case.
  • The two --no-api tests now assert OutputMethod.API not in output_methods rather than that no output_methods kwarg was passed.
  • Reverting the _configured_output_methods() call back to the hardcoded [CSV, API] makes four of these tests fail, so they are real guards.
  • uv run pytest tests/ -q --ignore=tests/test_viz_data.py and uv run pre-commit run --all-files both pass.

Closes #1323

🤖 Generated with Claude Code

The warning was emitted with stacklevel=2 from _resolve_output_methods, which
points at BaseEmissionsTracker.__init__ itself, so Python's default
ignore::DeprecationWarning filter dropped it for every caller outside __main__.
Compute the stack level dynamically instead, which also handles the extra frames
added by OfflineEmissionsTracker and the @Suppress decorator.

Migrate the CLI's own call sites to output_methods= so codecarbon no longer
trips its own deprecation check.

Closes #1323

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.77419% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 91.46%. Comparing base (065d0e6) to head (91f3285).

Files with missing lines Patch % Lines
codecarbon/emissions_tracker.py 92.85% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1346      +/-   ##
==========================================
+ Coverage   91.39%   91.46%   +0.06%     
==========================================
  Files          49       49              
  Lines        5056     5085      +29     
==========================================
+ Hits         4621     4651      +30     
+ Misses        435      434       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

output_methods= replaces where save_to_api= added, so a hardcoded
[CSV, API] dropped configured outputs and --no-api became a no-op.
@davidberenstein1957
davidberenstein1957 marked this pull request as ready for review August 13, 2026 05:05
@davidberenstein1957
davidberenstein1957 requested a review from a team as a code owner August 13, 2026 05:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

save_to_* DeprecationWarning has the wrong stacklevel and never reaches users

1 participant