From f4971afee90c32d1082a645ba097115f7141d59b Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 17 Nov 2021 19:04:23 -0500 Subject: [PATCH 1/9] FIX: include function_name in output filename is used This allows multiple functions from the same file to be called. --- lib/matplotlib/sphinxext/plot_directive.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/sphinxext/plot_directive.py b/lib/matplotlib/sphinxext/plot_directive.py index 671484790d66..2aa55a84233f 100644 --- a/lib/matplotlib/sphinxext/plot_directive.py +++ b/lib/matplotlib/sphinxext/plot_directive.py @@ -566,6 +566,8 @@ def render_figures(code, code_path, output_dir, output_base, context, Save the images under *output_dir* with file names derived from *output_base* """ + if function_name is not None: + output_base = f'{output_base}_{function_name}' formats = get_plot_formats(config) # Try to determine if all images already exist From 3c90d8f6d54c0910fde4296d0e0e581eae3b1eac Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 17 Nov 2021 19:05:35 -0500 Subject: [PATCH 2/9] API: only split the code on fully-dedented plt.show calls Also remove the () from the search so `plt.show(block=True)` will also be caught. The reason for this change is so that calling `plt.show()` inside of a function will not try to split the code into independently executed chunks. --- lib/matplotlib/sphinxext/plot_directive.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/sphinxext/plot_directive.py b/lib/matplotlib/sphinxext/plot_directive.py index 2aa55a84233f..261f2ae7d9cd 100644 --- a/lib/matplotlib/sphinxext/plot_directive.py +++ b/lib/matplotlib/sphinxext/plot_directive.py @@ -348,7 +348,7 @@ def _split_code_at_show(text): is_doctest = contains_doctest(text) part = [] for line in text.split("\n"): - if (not is_doctest and line.strip() == 'plt.show()') or \ + if (not is_doctest and line.startswith('plt.show')) or \ (is_doctest and line.strip() == '>>> plt.show()'): part.append(line) parts.append("\n".join(part)) From 51aa6e4516e05a173c9497f23012fca05850e033 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 17 Nov 2021 19:08:40 -0500 Subject: [PATCH 3/9] FIX: do not try to split the code if a function name is passed We do not know where it is safe to split the code if we are going to later call a function for the generated namespace. --- lib/matplotlib/sphinxext/plot_directive.py | 30 ++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/lib/matplotlib/sphinxext/plot_directive.py b/lib/matplotlib/sphinxext/plot_directive.py index 261f2ae7d9cd..13cddb0161cd 100644 --- a/lib/matplotlib/sphinxext/plot_directive.py +++ b/lib/matplotlib/sphinxext/plot_directive.py @@ -342,21 +342,25 @@ def split_code_at_show(text): return _split_code_at_show(text)[1] -def _split_code_at_show(text): +def _split_code_at_show(text, function_name): """Split code at plt.show().""" - parts = [] + is_doctest = contains_doctest(text) - part = [] - for line in text.split("\n"): - if (not is_doctest and line.startswith('plt.show')) or \ - (is_doctest and line.strip() == '>>> plt.show()'): - part.append(line) + if function_name is None: + parts = [] + part = [] + for line in text.split("\n"): + if (not is_doctest and line.startswith('plt.show')) or \ + (is_doctest and line.strip() == '>>> plt.show()'): + part.append(line) + parts.append("\n".join(part)) + part = [] + else: + part.append(line) + if "\n".join(part).strip(): parts.append("\n".join(part)) - part = [] - else: - part.append(line) - if "\n".join(part).strip(): - parts.append("\n".join(part)) + else: + parts = [text] return is_doctest, parts @@ -572,7 +576,7 @@ def render_figures(code, code_path, output_dir, output_base, context, # Try to determine if all images already exist - is_doctest, code_pieces = _split_code_at_show(code) + is_doctest, code_pieces = _split_code_at_show(code, function_name) # Look for single-figure output files first all_exists = True From 496888c092a8f35a8b8a0b553f926f7fbd3f4b9e Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 17 Nov 2021 19:37:19 -0500 Subject: [PATCH 4/9] MNT: pass all input to render_figures an keyword There are too many positional arguments and the names are subtly different. --- lib/matplotlib/sphinxext/plot_directive.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/sphinxext/plot_directive.py b/lib/matplotlib/sphinxext/plot_directive.py index 13cddb0161cd..9f4cc430d079 100644 --- a/lib/matplotlib/sphinxext/plot_directive.py +++ b/lib/matplotlib/sphinxext/plot_directive.py @@ -799,13 +799,13 @@ def run(arguments, content, options, state_machine, state, lineno): # make figures try: - results = render_figures(code, - source_file_name, - build_dir, - output_base, - keep_context, - function_name, - config, + results = render_figures(code=code, + code_path=source_file_name, + output_dir=build_dir, + output_base=output_base, + context=keep_context, + function_name=function_name, + config=config, context_reset=context_opt == 'reset', close_figs=context_opt == 'close-figs', code_includes=source_file_includes) From 1c6f55b8a882bc84977b75bbb1d4946613bb0792 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Wed, 17 Nov 2021 21:07:22 -0500 Subject: [PATCH 5/9] MNT: move helper function --- lib/matplotlib/tests/test_sphinxext.py | 34 +++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/lib/matplotlib/tests/test_sphinxext.py b/lib/matplotlib/tests/test_sphinxext.py index 7b8ab1eeff50..5bd988772687 100644 --- a/lib/matplotlib/tests/test_sphinxext.py +++ b/lib/matplotlib/tests/test_sphinxext.py @@ -14,6 +14,23 @@ minversion=None if sys.version_info < (3, 10) else '4.1.3') +def build_sphinx_html(source_dir, doctree_dir, html_dir, extra_args=None): + # Build the pages with warnings turned into errors + extra_args = [] if extra_args is None else extra_args + cmd = [sys.executable, '-msphinx', '-W', '-b', 'html', + '-d', str(doctree_dir), str(source_dir), str(html_dir), *extra_args] + proc = Popen(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True, + env={**os.environ, "MPLBACKEND": ""}) + out, err = proc.communicate() + + assert proc.returncode == 0, \ + f"sphinx build failed with stdout:\n{out}\nstderr:\n{err}\n" + if err: + pytest.fail(f"sphinx build emitted the following warnings:\n{err}") + + assert html_dir.is_dir() + + def test_tinypages(tmp_path): shutil.copytree(Path(__file__).parent / 'tinypages', tmp_path, dirs_exist_ok=True) @@ -159,20 +176,3 @@ def test_show_source_link_false(tmp_path, plot_html_show_source_link): build_sphinx_html(tmp_path, doctree_dir, html_dir, extra_args=[ '-D', f'plot_html_show_source_link={plot_html_show_source_link}']) assert len(list(html_dir.glob("**/index-1.py"))) == 0 - - -def build_sphinx_html(tmp_path, doctree_dir, html_dir, extra_args=None): - # Build the pages with warnings turned into errors - extra_args = [] if extra_args is None else extra_args - cmd = [sys.executable, '-msphinx', '-W', '-b', 'html', - '-d', str(doctree_dir), str(tmp_path), str(html_dir), *extra_args] - proc = Popen(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True, - env={**os.environ, "MPLBACKEND": ""}) - out, err = proc.communicate() - - assert proc.returncode == 0, \ - f"sphinx build failed with stdout:\n{out}\nstderr:\n{err}\n" - if err: - pytest.fail(f"sphinx build emitted the following warnings:\n{err}") - - assert html_dir.is_dir() From 9ff4f498943fa1b12419338baba50180b2dbc723 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 20 Oct 2022 19:31:59 -0400 Subject: [PATCH 6/9] TST: update tests to account for functions that plot --- lib/matplotlib/tests/test_sphinxext.py | 4 +++- lib/matplotlib/tests/tinypages/range6.py | 7 +++++++ lib/matplotlib/tests/tinypages/some_plots.rst | 5 ++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_sphinxext.py b/lib/matplotlib/tests/test_sphinxext.py index 5bd988772687..41575d3a3ce1 100644 --- a/lib/matplotlib/tests/test_sphinxext.py +++ b/lib/matplotlib/tests/test_sphinxext.py @@ -77,7 +77,7 @@ def plot_directive_file(num): assert b'# Only a comment' in html_contents # check plot defined in external file. assert filecmp.cmp(range_4, img_dir / 'range4.png') - assert filecmp.cmp(range_6, img_dir / 'range6.png') + assert filecmp.cmp(range_6, img_dir / 'range6_range6.png') # check if figure caption made it into html file assert b'This is the caption for plot 15.' in html_contents # check if figure caption using :caption: made it into html file @@ -91,6 +91,8 @@ def plot_directive_file(num): # Plot 21 is range(6) plot via an include directive. But because some of # the previous plots are repeated, the argument to plot_file() is only 17. assert filecmp.cmp(range_6, plot_file(17)) + # plot 22 is from the range6.py file again, but a different function + assert filecmp.cmp(range_10, img_dir / 'range6_range10.png') # Modify the included plot contents = (tmp_path / 'included_plot_21.rst').read_bytes() diff --git a/lib/matplotlib/tests/tinypages/range6.py b/lib/matplotlib/tests/tinypages/range6.py index fa5d035e4ab2..b6655ae07e1f 100644 --- a/lib/matplotlib/tests/tinypages/range6.py +++ b/lib/matplotlib/tests/tinypages/range6.py @@ -11,3 +11,10 @@ def range6(): plt.figure() plt.plot(range(6)) plt.show() + + +def range10(): + """The function that should be executed.""" + plt.figure() + plt.plot(range(10)) + plt.show() diff --git a/lib/matplotlib/tests/tinypages/some_plots.rst b/lib/matplotlib/tests/tinypages/some_plots.rst index 6f90c3976044..345e482b8abd 100644 --- a/lib/matplotlib/tests/tinypages/some_plots.rst +++ b/lib/matplotlib/tests/tinypages/some_plots.rst @@ -168,8 +168,11 @@ scenario: plt.figure() plt.plot(range(4)) - + Plot 21 is generated via an include directive: .. include:: included_plot_21.rst +Plot 22 uses a different specific function in a file with plot commands: + +.. plot:: range6.py range10 From fde3b9f1244e2380b82fecb4be9f0e06165cd023 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 20 Oct 2022 19:32:52 -0400 Subject: [PATCH 7/9] MNT: minor improvement to code Use `else` on for loop to be clearer about what break means. --- lib/matplotlib/sphinxext/plot_directive.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/sphinxext/plot_directive.py b/lib/matplotlib/sphinxext/plot_directive.py index 9f4cc430d079..ced109e8b2a3 100644 --- a/lib/matplotlib/sphinxext/plot_directive.py +++ b/lib/matplotlib/sphinxext/plot_directive.py @@ -579,7 +579,6 @@ def render_figures(code, code_path, output_dir, output_base, context, is_doctest, code_pieces = _split_code_at_show(code, function_name) # Look for single-figure output files first - all_exists = True img = ImageFile(output_base, output_dir) for format, dpi in formats: if context or out_of_date(code_path, img.filename(format), @@ -587,13 +586,14 @@ def render_figures(code, code_path, output_dir, output_base, context, all_exists = False break img.formats.append(format) + else: + all_exists = True if all_exists: return [(code, [img])] # Then look for multi-figure output files results = [] - all_exists = True for i, code_piece in enumerate(code_pieces): images = [] for j in itertools.count(): @@ -617,6 +617,8 @@ def render_figures(code, code_path, output_dir, output_base, context, if not all_exists: break results.append((code_piece, images)) + else: + all_exists = True if all_exists: return results From 23b5ee2f016772c88349ec8849daa89d69975a12 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 20 Oct 2022 19:00:51 -0400 Subject: [PATCH 8/9] MNT: expire mpl3.5 deprecation Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- .../next_api_changes/removals/21661-TAC.rst | 12 +++++ lib/matplotlib/sphinxext/plot_directive.py | 48 +------------------ lib/matplotlib/tests/tinypages/some_plots.rst | 4 +- 3 files changed, 14 insertions(+), 50 deletions(-) create mode 100644 doc/api/next_api_changes/removals/21661-TAC.rst diff --git a/doc/api/next_api_changes/removals/21661-TAC.rst b/doc/api/next_api_changes/removals/21661-TAC.rst new file mode 100644 index 000000000000..78c8837d6a8d --- /dev/null +++ b/doc/api/next_api_changes/removals/21661-TAC.rst @@ -0,0 +1,12 @@ +plot directive removals +~~~~~~~~~~~~~~~~~~~~~~~ + +The public methods: + +- ``matplotlib.sphinxext.split_code_at_show`` +- ``matplotlib.sphinxext.unescape_doctest`` +- ``matplotlib.sphinxext.run_code`` + +have been removed. + +The deprecated *encoding* option to the plot directive has been removed. diff --git a/lib/matplotlib/sphinxext/plot_directive.py b/lib/matplotlib/sphinxext/plot_directive.py index ced109e8b2a3..83fb9384e650 100644 --- a/lib/matplotlib/sphinxext/plot_directive.py +++ b/lib/matplotlib/sphinxext/plot_directive.py @@ -60,11 +60,6 @@ changed using the ``plot_html_show_source_link`` variable in :file:`conf.py` (which itself defaults to True). - ``:encoding:`` : str - If this source file is in a non-UTF8 or non-ASCII encoding, the - encoding must be specified using the ``:encoding:`` option. The - encoding will not be inferred using the ``-*- coding -*-`` metacomment. - ``:context:`` : bool or str If provided, the code will be run in the context of all previous plot directives for which the ``:context:`` option was specified. This only @@ -166,7 +161,7 @@ import matplotlib from matplotlib.backend_bases import FigureManagerBase import matplotlib.pyplot as plt -from matplotlib import _api, _pylab_helpers, cbook +from matplotlib import _pylab_helpers, cbook matplotlib.use("agg") @@ -200,11 +195,6 @@ def _option_format(arg): return directives.choice(arg, ('python', 'doctest')) -def _deprecated_option_encoding(arg): - _api.warn_deprecated("3.5", name="encoding", obj_type="option") - return directives.encoding(arg) - - def mark_plot_labels(app, document): """ To make plots referenceable, we need to move the reference from the @@ -254,7 +244,6 @@ class PlotDirective(Directive): 'format': _option_format, 'context': _option_context, 'nofigs': directives.flag, - 'encoding': _deprecated_option_encoding, 'caption': directives.unchanged, } @@ -316,32 +305,6 @@ def contains_doctest(text): return bool(m) -@_api.deprecated("3.5", alternative="doctest.script_from_examples") -def unescape_doctest(text): - """ - Extract code from a piece of text, which contains either Python code - or doctests. - """ - if not contains_doctest(text): - return text - code = "" - for line in text.split("\n"): - m = re.match(r'^\s*(>>>|\.\.\.) (.*)$', line) - if m: - code += m.group(2) + "\n" - elif line.strip(): - code += "# " + line.strip() + "\n" - else: - code += "\n" - return code - - -@_api.deprecated("3.5") -def split_code_at_show(text): - """Split code at plt.show().""" - return _split_code_at_show(text)[1] - - def _split_code_at_show(text, function_name): """Split code at plt.show().""" @@ -473,15 +436,6 @@ class PlotError(RuntimeError): pass -@_api.deprecated("3.5") -def run_code(code, code_path, ns=None, function_name=None): - """ - Import a Python module from a path, and run the function given by - name, if function_name is not None. - """ - _run_code(unescape_doctest(code), code_path, ns, function_name) - - def _run_code(code, code_path, ns=None, function_name=None): """ Import a Python module from a path, and run the function given by diff --git a/lib/matplotlib/tests/tinypages/some_plots.rst b/lib/matplotlib/tests/tinypages/some_plots.rst index 345e482b8abd..dd1f79892b0e 100644 --- a/lib/matplotlib/tests/tinypages/some_plots.rst +++ b/lib/matplotlib/tests/tinypages/some_plots.rst @@ -120,11 +120,9 @@ Plot 14 uses ``include-source``: # Only a comment -Plot 15 uses an external file with the plot commands and a caption (the -encoding is ignored and just verifies the deprecation is not broken): +Plot 15 uses an external file with the plot commands and a caption: .. plot:: range4.py - :encoding: utf-8 This is the caption for plot 15. From 1411ba3b22bab8185a070633b3c162dbb251b9d1 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sun, 18 Dec 2022 15:28:42 -0500 Subject: [PATCH 9/9] Be more careful about iding plt.show Co-authored-by: Elliott Sales de Andrade --- lib/matplotlib/sphinxext/plot_directive.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/sphinxext/plot_directive.py b/lib/matplotlib/sphinxext/plot_directive.py index 83fb9384e650..c942085e2159 100644 --- a/lib/matplotlib/sphinxext/plot_directive.py +++ b/lib/matplotlib/sphinxext/plot_directive.py @@ -313,8 +313,8 @@ def _split_code_at_show(text, function_name): parts = [] part = [] for line in text.split("\n"): - if (not is_doctest and line.startswith('plt.show')) or \ - (is_doctest and line.strip() == '>>> plt.show()'): + if ((not is_doctest and line.startswith('plt.show(')) or + (is_doctest and line.strip() == '>>> plt.show()')): part.append(line) parts.append("\n".join(part)) part = []