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 671484790d66..c942085e2159 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,47 +305,25 @@ 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): +def _split_code_at_show(text, function_name): """Split code at plt.show().""" - return _split_code_at_show(text)[1] - -def _split_code_at_show(text): - """Split code at plt.show().""" - parts = [] is_doctest = contains_doctest(text) - part = [] - for line in text.split("\n"): - if (not is_doctest and line.strip() == '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 @@ -469,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 @@ -566,14 +524,15 @@ 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 - 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 img = ImageFile(output_base, output_dir) for format, dpi in formats: if context or out_of_date(code_path, img.filename(format), @@ -581,13 +540,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(): @@ -611,6 +571,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 @@ -793,13 +755,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) diff --git a/lib/matplotlib/tests/test_sphinxext.py b/lib/matplotlib/tests/test_sphinxext.py index 7b8ab1eeff50..41575d3a3ce1 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) @@ -60,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 @@ -74,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() @@ -159,20 +178,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() 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..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. @@ -168,8 +166,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