8000 Simplify _api.warn_external on Python 3.12+ · matplotlib/matplotlib@9283ce5 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9283ce5

Browse files
committed
Simplify _api.warn_external on Python 3.12+
Python 3.12 added the `skip_file_prefixes` argument, which essentially does what this helper function does for us. Technically, I think the old implementation would set the stack level to the tests, if called in one, but this one doesn't. However, that shouldn't be a problem, as either 1) warnings are errors, or 2), we catch the warning with `pytest.warns` and don't see the stack level.
1 parent 6ff7f40 commit 9283ce5

File tree

2 files changed

+41
-20
lines changed

2 files changed

+41
-20
lines changed

lib/matplotlib/_api/__init__.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import functools
1414
import itertools
15+
import pathlib
1516
import re
1617
import sys
1718
import warnings
@@ -366,16 +367,25 @@ def warn_external(message, category=None):
366367
warnings.warn`` (or ``functools.partial(warnings.warn, stacklevel=2)``,
367368
etc.).
368369
"""
369-
frame = sys._getframe()
370-
for stacklevel in itertools.count(1):
371-
if frame is None:
372-
# when called in embedded context may hit frame is None
373-
break
374-
if not re.match(r"\A(matplotlib|mpl_toolkits)(\Z|\.(?!tests\.))",
375-
# Work around sphinx-gallery not setting __name__.
376-
frame.f_globals.get("__name__", "")):
377-
break
378-
frame = frame.f_back
379-
# preemptively break reference cycle between locals and the frame
380-
del frame
381-
warnings.warn(message, category, stacklevel)
370+
kwargs = {}
371+
if sys.version_info[:2] >= (3, 12):
372+
# Go to Python's `site-packages` or `lib` from an editable install.
373+
basedir = pathlib.Path(__file__).parents[2]
374+
kwargs['skip_file_prefixes'] = (str(basedir / 'matplotlib'),
375+
str(basedir / 'mpl_toolkits'))
376+
else:
377+
frame = sys._getframe()
378+
for stacklevel in itertools.count(1):
379+
if frame is None:
380+
# when called in embedded context may hit frame is None
381+
kwargs['stacklevel'] = stacklevel
382+
break
383+
if not re.match(r"\A(matplotlib|mpl_toolkits)(\Z|\.(?!tests\.))",
384+
# Work around sphinx-gallery not setting __name__.
385+
frame.f_globals.get("__name__", "")):
386+
kwargs['stacklevel'] = stacklevel
387+
break
388+
frame = frame.f_back
389+
# preemptively break reference cycle between locals and the frame
390+
del frame
391+
warnings.warn(message, category, **kwargs)

lib/matplotlib/tests/test_cbook.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from __future__ import annotations
22

3-
import sys
43
import itertools
4+
import pathlib
55
import pickle
6+
import sys
67

78
from typing import Any
89
from unittest.mock import patch, Mock
@@ -478,6 +479,22 @@ def test_normalize_kwargs_pass(inp, expected, kwargs_to_norm):
478479
assert expected == cbook.normalize_kwargs(inp, **kwargs_to_norm)
479480

480481

482+
def test_warn_external(recwarn):
483+
_api.warn_external("oops")
484+
assert len(recwarn) == 1
485+
if sys.version_info[:2] >= (3, 12):
486+
# With Python 3.12, we let Python figure out the stacklevel using the
487+
# `skip_file_prefixes` argument, which cannot exempt tests, so just confirm
488+
# the filename is not in the package.
489+
basedir = pathlib.Path(__file__).parents[2]
490+
assert not recwarn[0].filename.startswith((str(basedir / 'matplotlib'),
491+
str(basedir / 'mpl_toolkits')))
492+
else:
493+
# On older Python versions, we manually calculated the stacklevel, and had an
494+
# exception for our own tests.
495+
assert recwarn[0].filename == __file__
496+
497+
481498
def test_warn_external_frame_embedded_python():
482499
with patch.object(cbook, "sys") as mock_sys:
483500
mock_sys._getframe = Mock(return_value=None)
@@ -784,12 +801,6 @@ def test_safe_first_element_pandas_series(pd):
784801
assert actual == 0
785802

786803

787-
def test_warn_external(recwarn):
788-
_api.warn_external("oops")
789-
assert len(recwarn) == 1
790-
assert recwarn[0].filename == __file__
791-
792-
793804
def test_array_patch_perimeters():
794805
# This compares the old implementation as a reference for the
795806
# vectorized one.

0 commit comments

Comments
 (0)
0