From 2b43046b0320cb7042e7eda0f1c48b239e70d6cf Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Mon, 7 Mar 2022 13:53:55 +0100 Subject: [PATCH] Drop dependency on testpath. Instead, backport the tiny chdir contextmanager from CPython. --- nbconvert/exporters/pdf.py | 5 +- .../exporters/tests/test_templateexporter.py | 139 +++++++++--------- nbconvert/tests/base.py | 18 +-- nbconvert/utils/_contextlib_chdir.py | 20 +++ setup.py | 1 - 5 files changed, 100 insertions(+), 83 deletions(-) create mode 100644 nbconvert/utils/_contextlib_chdir.py diff --git a/nbconvert/exporters/pdf.py b/nbconvert/exporters/pdf.py index 420031cf3..193045cb0 100644 --- a/nbconvert/exporters/pdf.py +++ b/nbconvert/exporters/pdf.py @@ -6,12 +6,13 @@ import subprocess import os import sys +from tempfile import TemporaryDirectory import shutil from traitlets import Integer, List, Bool, Instance, Unicode, default -from testpath.tempdir import TemporaryWorkingDirectory from typing import Optional from .latex import LatexExporter +from ..utils import _contextlib_chdir class LatexFailed(IOError): """Exception for failed latex run @@ -175,7 +176,7 @@ def from_notebook_node(self, nb, resources=None, **kw): self.texinputs = os.getcwd() self._captured_outputs = [] - with TemporaryWorkingDirectory(): + with TemporaryDirectory() as td, _contextlib_chdir.chdir(td): notebook_name = 'notebook' resources['output_extension'] = '.tex' tex_file = self.writer.write(latex, resources, notebook_name=notebook_name) diff --git a/nbconvert/exporters/tests/test_templateexporter.py b/nbconvert/exporters/tests/test_templateexporter.py index aee6a60dd..276bd1520 100644 --- a/nbconvert/exporters/tests/test_templateexporter.py +++ b/nbconvert/exporters/tests/test_templateexporter.py @@ -14,6 +14,7 @@ from nbformat import v4 from unittest.mock import patch from concurrent.futures import ProcessPoolExecutor +from tempfile import TemporaryDirectory from .base import ExportersTestsBase from .cheese import CheesePreprocessor @@ -21,7 +22,7 @@ from ..rst import RSTExporter from ..html import HTMLExporter from ..markdown import MarkdownExporter -from testpath import tempdir +from ...utils import _contextlib_chdir import pytest @@ -128,7 +129,7 @@ def test_pickle(self): assert len(output) > 0 def test_absolute_template_file(self): - with tempdir.TemporaryDirectory() as td: + with TemporaryDirectory() as td: template = os.path.join(td, 'abstemplate.ext.j2') test_output = 'absolute!' with open(template, 'w') as f: @@ -140,22 +141,21 @@ def test_absolute_template_file(self): assert os.path.dirname(template) in exporter.template_paths def test_relative_template_file(self): - with tempdir.TemporaryWorkingDirectory() as td: - with patch('os.getcwd', return_value=os.path.abspath(td)): - template = os.path.join('relative', 'relative_template.ext.j2') - template_abs = os.path.abspath(os.path.join(td, template)) - os.mkdir(os.path.dirname(template_abs)) - test_output = 'relative!' - with open(template_abs, 'w') as f: - f.write(test_output) - config = Config() - config.TemplateExporter.template_file = template - exporter = self._make_exporter(config=config) - assert os.path.abspath(exporter.template.filename) == template_abs - assert os.path.dirname(template_abs) in [os.path.abspath(d) for d in exporter.template_paths] + with TemporaryDirectory() as td, _contextlib_chdir.chdir(td): + template = os.path.join('relative', 'relative_template.ext.j2') + template_abs = os.path.abspath(os.path.join(td, template)) + os.mkdir(os.path.dirname(template_abs)) + test_output = 'relative!' + with open(template_abs, 'w') as f: + f.write(test_output) + config = Config() + config.TemplateExporter.template_file = template + exporter = self._make_exporter(config=config) + assert os.path.abspath(exporter.template.filename) == template_abs + assert os.path.dirname(template_abs) in [os.path.abspath(d) for d in exporter.template_paths] def test_absolute_template_file_compatibility(self): - with tempdir.TemporaryDirectory() as td: + with TemporaryDirectory() as td: template = os.path.join(td, 'abstemplate.tpl') test_output = 'absolute!' with open(template, 'w') as f: @@ -168,23 +168,22 @@ def test_absolute_template_file_compatibility(self): assert os.path.dirname(template) in exporter.template_paths def test_relative_template_file_compatibility(self): - with tempdir.TemporaryWorkingDirectory() as td: - with patch('os.getcwd', return_value=os.path.abspath(td)): - template = os.path.join('relative', 'relative_template.tpl') - template_abs = os.path.abspath(os.path.join(td, template)) - os.mkdir(os.path.dirname(template_abs)) - test_output = 'relative!' - with open(template_abs, 'w') as f: - f.write(test_output) - config = Config() - config.TemplateExporter.template_file = template - with pytest.warns(DeprecationWarning): - exporter = self._make_exporter(config=config) - assert os.path.abspath(exporter.template.filename) == template_abs - assert os.path.dirname(template_abs) in [os.path.abspath(d) for d in exporter.template_paths] + with TemporaryDirectory() as td, _contextlib_chdir.chdir(td): + template = os.path.join('relative', 'relative_template.tpl') + template_abs = os.path.abspath(os.path.join(td, template)) + os.mkdir(os.path.dirname(template_abs)) + test_output = 'relative!' + with open(template_abs, 'w') as f: + f.write(test_output) + config = Config() + config.TemplateExporter.template_file = template + with pytest.warns(DeprecationWarning): + exporter = self._make_exporter(config=config) + assert os.path.abspath(exporter.template.filename) == template_abs + assert os.path.dirname(template_abs) in [os.path.abspath(d) for d in exporter.template_paths] def test_absolute_template_name_tpl_compatibility(self): - with tempdir.TemporaryDirectory() as td: + with TemporaryDirectory() as td: template = os.path.join(td, 'abstemplate.tpl') test_output = 'absolute!' with open(template, 'w') as f: @@ -218,22 +217,21 @@ def test_absolute_template_name_5x_compatibility_display_priority(self): # Can't use @pytest.mark.parametrize without removing all self.assert calls in all tests... repeating some here def relative_template_test(self, template): - with tempdir.TemporaryWorkingDirectory() as td: - with patch('os.getcwd', return_value=os.path.abspath(td)): - template_abs = os.path.abspath(os.path.join(td, template)) - dirname = os.path.dirname(template_abs) - if not os.path.exists(dirname): - os.mkdir(dirname) - test_output = 'relative!' - with open(template_abs, 'w') as f: - f.write(test_output) - config = Config() - # We're setting the template_name instead of the template_file - config.TemplateExporter.template_name = template - with pytest.warns(DeprecationWarning): - exporter = self._make_exporter(config=config) - assert os.path.abspath(exporter.template.filename) == template_abs - assert os.path.dirname(template_abs) in [os.path.abspath(d) for d in exporter.template_paths] + with TemporaryDirectory() as td, _contextlib_chdir.chdir(td): + template_abs = os.path.abspath(os.path.join(td, template)) + dirname = os.path.dirname(template_abs) + if not os.path.exists(dirname): + os.mkdir(dirname) + test_output = 'relative!' + with open(template_abs, 'w') as f: + f.write(test_output) + config = Config() + # We're setting the template_name instead of the template_file + config.TemplateExporter.template_name = template + with pytest.warns(DeprecationWarning): + exporter = self._make_exporter(config=config) + assert os.path.abspath(exporter.template.filename) == template_abs + assert os.path.dirname(template_abs) in [os.path.abspath(d) for d in exporter.template_paths] def test_relative_template_name_tpl_compatibility_local(self): self.relative_template_test('relative_template.tpl') @@ -248,7 +246,7 @@ def test_relative_template_name_tpl_compatibility_dot_nested(self): self.relative_template_test(os.path.join('.', 'relative', 'relative_template.tpl')) def test_absolute_template_dir(self): - with tempdir.TemporaryDirectory() as td: + with TemporaryDirectory() as td: template = 'mytemplate' template_file = os.path.join(td, template, 'index.py.j2') template_dir = os.path.dirname(template_file) @@ -265,29 +263,28 @@ def test_absolute_template_dir(self): assert os.path.join(td, template) in exporter.template_paths def test_local_template_dir(self): - with tempdir.TemporaryWorkingDirectory() as td: - with patch('os.getcwd', return_value=os.path.abspath(td)): - template = 'mytemplate' - template_file = os.path.join(template, 'index.py.j2') - template_abs = os.path.abspath(os.path.join(td, template_file)) - template_conf = os.path.abspath(os.path.join(td, template, 'conf.json')) - os.mkdir(os.path.dirname(template_abs)) - test_output = 'local!' - with open(template_abs, 'w') as f: - f.write(test_output) - with open(template_conf, 'w') as f: - # Mimic having a superset of accepted mimetypes - f.write(json.dumps(Config(mimetypes={ - "text/x-python": True, - "text/html": True, - } - ))) - config = Config() - config.TemplateExporter.template_name = template - exporter = self._make_exporter(config=config) - assert os.path.abspath(exporter.template.filename) == template_abs - assert exporter.template_name == template - assert os.path.join(td, template) in exporter.template_paths + with TemporaryDirectory() as td, _contextlib_chdir.chdir(td): + template = 'mytemplate' + template_file = os.path.join(template, 'index.py.j2') + template_abs = os.path.abspath(os.path.join(td, template_file)) + template_conf = os.path.abspath(os.path.join(td, template, 'conf.json')) + os.mkdir(os.path.dirname(template_abs)) + test_output = 'local!' + with open(template_abs, 'w') as f: + f.write(test_output) + with open(template_conf, 'w') as f: + # Mimic having a superset of accepted mimetypes + f.write(json.dumps(Config(mimetypes={ + "text/x-python": True, + "text/html": True, + } + ))) + config = Config() + config.TemplateExporter.template_name = template + exporter = self._make_exporter(config=config) + assert os.path.abspath(exporter.template.filename) == template_abs + assert exporter.template_name == template + assert os.path.join(td, template) in exporter.template_paths def test_local_template_file_extending_lab(self): template_file = os.path.join(self._get_files_path(), 'lablike.html.j2') diff --git a/nbconvert/tests/base.py b/nbconvert/tests/base.py index 9f6d7a8e0..c5bfe4147 100644 --- a/nbconvert/tests/base.py +++ b/nbconvert/tests/base.py @@ -3,6 +3,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import contextlib import io import os import glob @@ -12,9 +13,11 @@ import unittest import nbconvert from subprocess import Popen, PIPE +from tempfile import TemporaryDirectory from nbformat import v4, write -from testpath.tempdir import TemporaryWorkingDirectory + +from ..utils import _contextlib_chdir class TestsBase(unittest.TestCase): @@ -86,15 +89,12 @@ def recursive_replace(self, text, search, replacement): text = text.replace(search, replacement) return text + @contextlib.contextmanager def create_temp_cwd(self, copy_filenames=None): - temp_dir = TemporaryWorkingDirectory() - - #Copy the files if requested. - if copy_filenames is not None: - self.copy_files_to(copy_filenames, dest=temp_dir.name) - - #Return directory handler - return temp_dir + with TemporaryDirectory() as td, _contextlib_chdir.chdir(td): + if copy_filenames is not None: # Copy the files if requested. + self.copy_files_to(copy_filenames, dest=td) + yield td # Return directory handler @classmethod def merge_dicts(cls, *dict_args): diff --git a/nbconvert/utils/_contextlib_chdir.py b/nbconvert/utils/_contextlib_chdir.py new file mode 100644 index 000000000..70f1ee997 --- /dev/null +++ b/nbconvert/utils/_contextlib_chdir.py @@ -0,0 +1,20 @@ +"""Backport of Python 3.11's contextlib.chdir.""" + + +from contextlib import AbstractContextManager +import os + + +class chdir(AbstractContextManager): + """Non thread-safe context manager to change the current working directory.""" + + def __init__(self, path): + self.path = path + self._old_cwd = [] + + def __enter__(self): + self._old_cwd.append(os.getcwd()) + os.chdir(self.path) + + def __exit__(self, *excinfo): + os.chdir(self._old_cwd.pop()) diff --git a/setup.py b/setup.py index 1aad5035b..80ee6471b 100644 --- a/setup.py +++ b/setup.py @@ -222,7 +222,6 @@ def get_data_files(): 'entrypoints>=0.2.2', 'bleach', 'pandocfilters>=1.4.1', - 'testpath', 'defusedxml', 'nbclient>=0.5.0,<0.6.0' ]