From daaf8856f8f5cf1211f082153c8db8333ab0ca62 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 24 Feb 2022 14:28:24 -0500 Subject: [PATCH 1/7] add flit backend --- jupyter_packaging/build_api.py | 6 +- jupyter_packaging/build_flit.py | 98 ++++++++++++++++++++++ setup.cfg | 3 +- tests/test_build_flit.py | 144 ++++++++++++++++++++++++++++++++ 4 files changed, 247 insertions(+), 4 deletions(-) create mode 100644 jupyter_packaging/build_flit.py create mode 100644 tests/test_build_flit.py diff --git a/jupyter_packaging/build_api.py b/jupyter_packaging/build_api.py index a38b2c4..d694da7 100644 --- a/jupyter_packaging/build_api.py +++ b/jupyter_packaging/build_api.py @@ -12,7 +12,7 @@ build_sdist as orig_build_sdist, build_wheel as orig_build_wheel, ) -import tomlkit +import tomli from jupyter_packaging.setupbase import __version__ @@ -55,7 +55,7 @@ def _get_build_func(): pyproject = Path("pyproject.toml") if not pyproject.exists(): return - data = tomlkit.loads(pyproject.read_text(encoding="utf-8")) + data = tomli.loads(pyproject.read_text(encoding="utf-8")) if "tool" not in data: return if "jupyter-packaging" not in data["tool"]: @@ -100,7 +100,7 @@ def _ensure_targets(): pyproject = Path("pyproject.toml") if not pyproject.exists(): return - data = tomlkit.loads(pyproject.read_text(encoding="utf-8")) + data = tomli.loads(pyproject.read_text(encoding="utf-8")) if "tool" not in data: return if "jupyter-packaging" not in data["tool"]: diff --git a/jupyter_packaging/build_flit.py b/jupyter_packaging/build_flit.py new file mode 100644 index 0000000..5002134 --- /dev/null +++ b/jupyter_packaging/build_flit.py @@ -0,0 +1,98 @@ +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. +import importlib +from pathlib import Path +import sys + +from flit.build_api import ( + get_requires_for_build_wheel, + get_requires_for_build_sdist, + prepare_metadata_for_build_wheel, + build_sdist as orig_build_sdist, + build_wheel as orig_build_wheel, +) +import tomli + + +VERSION = "0.11.1" + + +def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): + """Build a wheel with an optional pre-build step.""" + builder = _get_build_func() + if builder: + builder() + val = orig_build_wheel( + wheel_directory, + config_settings=config_settings, + metadata_directory=metadata_directory, + ) + _ensure_targets() + return val + + +def build_sdist(sdist_directory, config_settings=None): + """Build an sdist with an optional pre-build step.""" + builder = _get_build_func() + if builder: + builder() + val = orig_build_sdist(sdist_directory, config_settings=config_settings) + _ensure_targets() + return val + + + +def _get_build_func(): + pyproject = Path("pyproject.toml") + if not pyproject.exists(): + return + data = tomli.loads(pyproject.read_text(encoding="utf-8")) + if "tool" not in data: + return + if "jupyter-packaging" not in data["tool"]: + return + if "builder" not in data["tool"]["jupyter-packaging"]: + return + section = data["tool"]["jupyter-packaging"] + + if "factory" not in section["builder"]: + raise ValueError("Missing `factory` specifier for builder") + + factory_data = section["builder"]["factory"] + mod_name, _, factory_name = factory_data.rpartition(".") + + if "options" in section and "skip-if-exists" in section["options"]: + skip_if_exists = section["options"]["skip-if-exists"] + if all(Path(path).exists() for path in skip_if_exists): + return None + + # If the module fails to import, try importing as a local script + try: + mod = importlib.import_module(mod_name) + except ImportError: + try: + sys.path.insert(0, str(Path.cwd())) + mod = importlib.import_module(mod_name) + finally: + sys.path.pop(0) + + factory = getattr(mod, factory_name) + kwargs = section.get("build-args", {}) + return factory(**kwargs) + + +def _ensure_targets(): + pyproject = Path("pyproject.toml") + if not pyproject.exists(): + return + data = tomli.loads(pyproject.read_text(encoding="utf-8")) + if "tool" not in data: + return + if "jupyter-packaging" not in data["tool"]: + return + section = data["tool"]["jupyter-packaging"] + if "options" in section and "ensured-targets" in section["options"]: + targets = section["options"]["ensured-targets"] + missing = [t for t in targets if not Path(t).exists()] + if missing: + raise ValueError(("missing files: %s" % missing)) diff --git a/setup.cfg b/setup.cfg index 32a12de..76ed37f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,7 +28,8 @@ packages = find: python_requires = >=3.7 install_requires = packaging - tomlkit + flit + tomli>=1.0 setuptools>=60.2.0 wheel deprecation diff --git a/tests/test_build_flit.py b/tests/test_build_flit.py new file mode 100644 index 0000000..ef0d0a3 --- /dev/null +++ b/tests/test_build_flit.py @@ -0,0 +1,144 @@ +import os +import sys +from subprocess import check_call +from unittest.mock import patch, call + +import pytest + +from jupyter_packaging.build_flit import build_wheel, build_sdist + + +TOML_CONTENT = """ +[tool.jupyter-packaging.builder] +factory = "foo.main" + +[tool.jupyter-packaging.build-args] +fizz = "buzz" +""" + +FOO_CONTENT = r""" +from pathlib import Path +def main(fizz=None): + Path('foo.txt').write_text(f'fizz={fizz}', encoding='utf-8') +""" + +BAD_CONTENT = """ +[tool.jupyter-packaging.builder] +bar = "foo.main" +""" + +ENSURED_CONTENT = """ +[tool.jupyter-packaging.options] +ensured-targets = ["foo.txt"] +""" + +SKIP_IF_EXISTS = """ +[tool.jupyter-packaging.options] +skip-if-exists = ["foo.txt"] +""" + + +def test_build_wheel_no_toml(tmp_path): + os.chdir(tmp_path) + orig_wheel = patch("jupyter_packaging.build_flit.orig_build_wheel") + build_wheel(tmp_path) + orig_wheel.assert_called_with( + tmp_path, config_settings=None, metadata_directory=None + ) + + +def test_build_wheel(tmp_path, mocker): + os.chdir(tmp_path) + tmp_path.joinpath("foo.py").write_text(FOO_CONTENT) + tmp_path.joinpath("pyproject.toml").write_text( + TOML_CONTENT + ENSURED_CONTENT, encoding="utf-8" + ) + orig_wheel = mocker.patch("jupyter_packaging.build_flit.orig_build_wheel") + build_wheel(tmp_path) + orig_wheel.assert_called_with( + tmp_path, config_settings=None, metadata_directory=None + ) + data = tmp_path.joinpath("foo.txt").read_text(encoding="utf-8") + assert data == "fizz=buzz" + + content = TOML_CONTENT.replace("buzz", "fizz") + SKIP_IF_EXISTS + tmp_path.joinpath("pyproject.toml").write_text(content, encoding="utf-8") + build_wheel(tmp_path) + data = tmp_path.joinpath("foo.txt").read_text(encoding="utf-8") + assert data == "fizz=buzz" + + +def test_build_wheel_bad_toml(tmp_path, mocker): + os.chdir(tmp_path) + tmp_path.joinpath("foo.py").write_text(FOO_CONTENT) + tmp_path.joinpath("pyproject.toml").write_text(BAD_CONTENT, encoding="utf-8") + orig_wheel = mocker.patch("jupyter_packaging.build_flit.orig_build_wheel") + with pytest.raises(ValueError): + build_wheel(tmp_path) + orig_wheel.assert_not_called() + + +def test_build_wheel_no_toml(tmp_path, mocker): + os.chdir(tmp_path) + orig_wheel = mocker.patch("jupyter_packaging.build_flit.orig_build_wheel") + build_wheel(tmp_path) + orig_wheel.assert_called_with( + tmp_path, config_settings=None, metadata_directory=None + ) + + +def test_build_sdist(tmp_path, mocker): + os.chdir(tmp_path) + tmp_path.joinpath("foo.py").write_text(FOO_CONTENT) + tmp_path.joinpath("pyproject.toml").write_text( + TOML_CONTENT + ENSURED_CONTENT, encoding="utf-8" + ) + orig_sdist = mocker.patch("jupyter_packaging.build_flit.orig_build_sdist") + build_sdist(tmp_path) + orig_sdist.assert_called_with(tmp_path, config_settings=None) + data = tmp_path.joinpath("foo.txt").read_text(encoding="utf-8") + assert data == "fizz=buzz" + + +def test_build_sdist_bad_toml(tmp_path, mocker): + os.chdir(tmp_path) + tmp_path.joinpath("foo.py").write_text(FOO_CONTENT) + tmp_path.joinpath("pyproject.toml").write_text(BAD_CONTENT, encoding="utf-8") + orig_sdist = mocker.patch("jupyter_packaging.build_flit.orig_build_sdist") + with pytest.raises(ValueError): + build_sdist(tmp_path) + orig_sdist.assert_not_called() + + +def test_build_sdist_no_toml(tmp_path, mocker): + os.chdir(tmp_path) + orig_sdist = mocker.patch("jupyter_packaging.build_flit.orig_build_sdist") + build_sdist(tmp_path) + orig_sdist.assert_called_with(tmp_path, config_settings=None) + + +def test_build_package(make_package): + package_dir = make_package() + pyproject = package_dir / "pyproject.toml" + text = pyproject.read_text(encoding="utf-8") + text = text.replace("setuptools.build_meta", "jupyter_packaging.build_flit") + text += TOML_CONTENT + pyproject.write_text(text, encoding="utf-8") + package_dir.joinpath("foo.py").write_text(FOO_CONTENT, encoding="utf-8") + check_call([sys.executable, "-m", "build"], cwd=package_dir) + data = package_dir.joinpath("foo.txt").read_text(encoding="utf-8") + assert data == "fizz=buzz" + + +def test_deprecated_metadata(make_package): + package_dir = make_package() + pyproject = package_dir / "pyproject.toml" + text = pyproject.read_text(encoding="utf-8") + text = text.replace("setuptools.build_meta", "jupyter_packaging.build_flit") + text += TOML_CONTENT + text = text.replace("factory =", "func =") + pyproject.write_text(text, encoding="utf-8") + package_dir.joinpath("foo.py").write_text(FOO_CONTENT, encoding="utf-8") + check_call([sys.executable, "-m", "build"], cwd=package_dir) + data = package_dir.joinpath("foo.txt").read_text(encoding="utf-8") + assert data == "fizz=buzz" From 93aee00097dca4e06086188c3a408604097035c0 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 24 Feb 2022 14:31:58 -0500 Subject: [PATCH 2/7] lint --- jupyter_packaging/build_flit.py | 1 - 1 file changed, 1 deletion(-) diff --git a/jupyter_packaging/build_flit.py b/jupyter_packaging/build_flit.py index 5002134..fef55a8 100644 --- a/jupyter_packaging/build_flit.py +++ b/jupyter_packaging/build_flit.py @@ -41,7 +41,6 @@ def build_sdist(sdist_directory, config_settings=None): return val - def _get_build_func(): pyproject = Path("pyproject.toml") if not pyproject.exists(): From 2fe8c702964bc32c7e86600afc3856e949de574d Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 24 Feb 2022 14:34:54 -0500 Subject: [PATCH 3/7] fix import --- jupyter_packaging/build_flit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jupyter_packaging/build_flit.py b/jupyter_packaging/build_flit.py index fef55a8..c1815d0 100644 --- a/jupyter_packaging/build_flit.py +++ b/jupyter_packaging/build_flit.py @@ -4,7 +4,7 @@ from pathlib import Path import sys -from flit.build_api import ( +from flit.buildapi import ( get_requires_for_build_wheel, get_requires_for_build_sdist, prepare_metadata_for_build_wheel, From 0b7d34eb27340e18c75e545a5bd55d256dd2b2c0 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 24 Feb 2022 14:55:52 -0500 Subject: [PATCH 4/7] update tests --- tests/test_build_flit.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/tests/test_build_flit.py b/tests/test_build_flit.py index ef0d0a3..756bd73 100644 --- a/tests/test_build_flit.py +++ b/tests/test_build_flit.py @@ -9,6 +9,11 @@ TOML_CONTENT = """ +[project] +name = "foo" +version = "0.1.0" +description = "foo package" + [tool.jupyter-packaging.builder] factory = "foo.main" @@ -128,17 +133,3 @@ def test_build_package(make_package): check_call([sys.executable, "-m", "build"], cwd=package_dir) data = package_dir.joinpath("foo.txt").read_text(encoding="utf-8") assert data == "fizz=buzz" - - -def test_deprecated_metadata(make_package): - package_dir = make_package() - pyproject = package_dir / "pyproject.toml" - text = pyproject.read_text(encoding="utf-8") - text = text.replace("setuptools.build_meta", "jupyter_packaging.build_flit") - text += TOML_CONTENT - text = text.replace("factory =", "func =") - pyproject.write_text(text, encoding="utf-8") - package_dir.joinpath("foo.py").write_text(FOO_CONTENT, encoding="utf-8") - check_call([sys.executable, "-m", "build"], cwd=package_dir) - data = package_dir.joinpath("foo.txt").read_text(encoding="utf-8") - assert data == "fizz=buzz" From 4650b6b6b260c1f4b63a47f4fea2e78e1bc8bd6a Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 24 Feb 2022 15:07:04 -0500 Subject: [PATCH 5/7] add build editable and tests --- jupyter_packaging/build_flit.py | 15 ++++++++++++++ tests/test_build_flit.py | 35 ++++++++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/jupyter_packaging/build_flit.py b/jupyter_packaging/build_flit.py index c1815d0..fefc5a1 100644 --- a/jupyter_packaging/build_flit.py +++ b/jupyter_packaging/build_flit.py @@ -10,6 +10,7 @@ prepare_metadata_for_build_wheel, build_sdist as orig_build_sdist, build_wheel as orig_build_wheel, + build_editable as orig_build_editable, ) import tomli @@ -41,6 +42,20 @@ def build_sdist(sdist_directory, config_settings=None): return val +def build_editable(wheel_directory, config_settings=None, metadata_directory=None): + """Build in editable mode pre-build step.""" + builder = _get_build_func() + if builder: + builder() + val = orig_build_editable( + wheel_directory, + config_settings=config_settings, + metadata_directory=metadata_directory, + ) + _ensure_targets() + return val + + def _get_build_func(): pyproject = Path("pyproject.toml") if not pyproject.exists(): diff --git a/tests/test_build_flit.py b/tests/test_build_flit.py index 756bd73..fd99eb2 100644 --- a/tests/test_build_flit.py +++ b/tests/test_build_flit.py @@ -5,7 +5,7 @@ import pytest -from jupyter_packaging.build_flit import build_wheel, build_sdist +from jupyter_packaging.build_flit import build_wheel, build_sdist, build_editable TOML_CONTENT = """ @@ -92,6 +92,27 @@ def test_build_wheel_no_toml(tmp_path, mocker): ) +def test_build_editable(tmp_path, mocker): + os.chdir(tmp_path) + tmp_path.joinpath("foo.py").write_text(FOO_CONTENT) + tmp_path.joinpath("pyproject.toml").write_text( + TOML_CONTENT + ENSURED_CONTENT, encoding="utf-8" + ) + orig_wheel = mocker.patch("jupyter_packaging.build_flit.orig_build_wheel") + build_wheel(tmp_path) + orig_wheel.assert_called_with( + tmp_path, config_settings=None, metadata_directory=None + ) + data = tmp_path.joinpath("foo.txt").read_text(encoding="utf-8") + assert data == "fizz=buzz" + + content = TOML_CONTENT.replace("buzz", "fizz") + SKIP_IF_EXISTS + tmp_path.joinpath("pyproject.toml").write_text(content, encoding="utf-8") + build_editable(tmp_path) + data = tmp_path.joinpath("foo.txt").read_text(encoding="utf-8") + assert data == "fizz=buzz" + + def test_build_sdist(tmp_path, mocker): os.chdir(tmp_path) tmp_path.joinpath("foo.py").write_text(FOO_CONTENT) @@ -133,3 +154,15 @@ def test_build_package(make_package): check_call([sys.executable, "-m", "build"], cwd=package_dir) data = package_dir.joinpath("foo.txt").read_text(encoding="utf-8") assert data == "fizz=buzz" + + +def test_develop_package(make_package): + package_dir = make_package() + pyproject = package_dir / "pyproject.toml" + text = pyproject.read_text(encoding="utf-8") + text = text.replace("setuptools.build_meta", "jupyter_packaging.build_flit") + text += TOML_CONTENT + text += "" + pyproject.write_text(text, encoding="utf-8") + package_dir.joinpath("foo.py").write_text(FOO_CONTENT, encoding="utf-8") + check_call([sys.executable, "-m", "pip", "install", "-e", "."], cwd=package_dir) From 45bd982d0b22f3f53fabaaaa598049748006f29a Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 24 Feb 2022 16:08:05 -0500 Subject: [PATCH 6/7] add alias for external-data --- jupyter_packaging/build_flit.py | 32 ++++++++++++++++++++++++------- tests/test_build_flit.py | 34 +++++++++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 11 deletions(-) diff --git a/jupyter_packaging/build_flit.py b/jupyter_packaging/build_flit.py index fefc5a1..25950ce 100644 --- a/jupyter_packaging/build_flit.py +++ b/jupyter_packaging/build_flit.py @@ -17,12 +17,16 @@ VERSION = "0.11.1" +# PEP 517 specifies that the CWD will always be the source tree +pyproj_toml = Path("pyproject.toml") + def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): """Build a wheel with an optional pre-build step.""" builder = _get_build_func() if builder: builder() + _replace_keys() val = orig_build_wheel( wheel_directory, config_settings=config_settings, @@ -37,6 +41,7 @@ def build_sdist(sdist_directory, config_settings=None): builder = _get_build_func() if builder: builder() + _replace_keys() val = orig_build_sdist(sdist_directory, config_settings=config_settings) _ensure_targets() return val @@ -44,9 +49,10 @@ def build_sdist(sdist_directory, config_settings=None): def build_editable(wheel_directory, config_settings=None, metadata_directory=None): """Build in editable mode pre-build step.""" - builder = _get_build_func() + builder = _get_build_func("editable-build") or _get_build_func("build") if builder: builder() + _replace_keys() val = orig_build_editable( wheel_directory, config_settings=config_settings, @@ -56,23 +62,25 @@ def build_editable(wheel_directory, config_settings=None, metadata_directory=Non return val -def _get_build_func(): +def _get_build_func(prefix="build"): pyproject = Path("pyproject.toml") if not pyproject.exists(): return + target = prefix + "er" + data = tomli.loads(pyproject.read_text(encoding="utf-8")) if "tool" not in data: return if "jupyter-packaging" not in data["tool"]: return - if "builder" not in data["tool"]["jupyter-packaging"]: + if target not in data["tool"]["jupyter-packaging"]: return section = data["tool"]["jupyter-packaging"] - if "factory" not in section["builder"]: - raise ValueError("Missing `factory` specifier for builder") + if "factory" not in section[target]: + raise ValueError(f"Missing `factory` specifier for {target}") - factory_data = section["builder"]["factory"] + factory_data = section[target]["factory"] mod_name, _, factory_name = factory_data.rpartition(".") if "options" in section and "skip-if-exists" in section["options"]: @@ -91,7 +99,7 @@ def _get_build_func(): sys.path.pop(0) factory = getattr(mod, factory_name) - kwargs = section.get("build-args", {}) + kwargs = section.get(f"{prefix}-args", {}) return factory(**kwargs) @@ -110,3 +118,13 @@ def _ensure_targets(): missing = [t for t in targets if not Path(t).exists()] if missing: raise ValueError(("missing files: %s" % missing)) + + +def _replace_keys(): + if not pyproj_toml.exists(): + return + data = pyproj_toml.read_text(encoding="utf-8") + before = "[tool][jupyter-packaging][external-data]" + after = "[tool][flit][external-data]" + data = data.replace(before, after) + pyproj_toml.write_text(data, encoding="utf-8") diff --git a/tests/test_build_flit.py b/tests/test_build_flit.py index fd99eb2..ae7b901 100644 --- a/tests/test_build_flit.py +++ b/tests/test_build_flit.py @@ -1,7 +1,8 @@ import os +from pathlib import Path import sys from subprocess import check_call -from unittest.mock import patch, call +from unittest.mock import patch import pytest @@ -21,6 +22,11 @@ fizz = "buzz" """ +DATA_CONTENT = """ +[tool.jupyter-packaging.external-data] +directory = "data" +""" + FOO_CONTENT = r""" from pathlib import Path def main(fizz=None): @@ -56,7 +62,7 @@ def test_build_wheel(tmp_path, mocker): os.chdir(tmp_path) tmp_path.joinpath("foo.py").write_text(FOO_CONTENT) tmp_path.joinpath("pyproject.toml").write_text( - TOML_CONTENT + ENSURED_CONTENT, encoding="utf-8" + TOML_CONTENT + DATA_CONTENT + ENSURED_CONTENT, encoding="utf-8" ) orig_wheel = mocker.patch("jupyter_packaging.build_flit.orig_build_wheel") build_wheel(tmp_path) @@ -149,6 +155,14 @@ def test_build_package(make_package): text = pyproject.read_text(encoding="utf-8") text = text.replace("setuptools.build_meta", "jupyter_packaging.build_flit") text += TOML_CONTENT + text += DATA_CONTENT + data_dir = package_dir / "data/etc/jupyter/jupyter_server_config.d" + data_dir.mkdir(parents=True) + data_file = data_dir / "jupyter_server_foo.json" + data_file.write_text( + '{"ServerApp": {"jpserver_extensions": {"jupyter_server_foo": true}}}"', + encoding="utf-8", + ) pyproject.write_text(text, encoding="utf-8") package_dir.joinpath("foo.py").write_text(FOO_CONTENT, encoding="utf-8") check_call([sys.executable, "-m", "build"], cwd=package_dir) @@ -161,8 +175,20 @@ def test_develop_package(make_package): pyproject = package_dir / "pyproject.toml" text = pyproject.read_text(encoding="utf-8") text = text.replace("setuptools.build_meta", "jupyter_packaging.build_flit") - text += TOML_CONTENT - text += "" + text += TOML_CONTENT.replace(".build", ".editable-build") + text += DATA_CONTENT + data_dir = package_dir / "data/etc/jupyter/jupyter_server_config.d" + data_dir.mkdir(parents=True) + data_file = data_dir / "jupyter_server_foo.json" + data_file.write_text( + '{"ServerApp": {"jpserver_extensions": {"jupyter_server_foo": true}}}"', + encoding="utf-8", + ) pyproject.write_text(text, encoding="utf-8") package_dir.joinpath("foo.py").write_text(FOO_CONTENT, encoding="utf-8") check_call([sys.executable, "-m", "pip", "install", "-e", "."], cwd=package_dir) + target = ( + Path(sys.base_prefix) + / "etc/jupyter/jupyter_server_config.d/jupyter_server_foo.json" + ) + assert target.exists() From d43c593c0bbafd04211fa43b68e06fafd3395e41 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 24 Feb 2022 16:14:42 -0500 Subject: [PATCH 7/7] fix replacement --- jupyter_packaging/build_flit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jupyter_packaging/build_flit.py b/jupyter_packaging/build_flit.py index 25950ce..9eead3e 100644 --- a/jupyter_packaging/build_flit.py +++ b/jupyter_packaging/build_flit.py @@ -124,7 +124,7 @@ def _replace_keys(): if not pyproj_toml.exists(): return data = pyproj_toml.read_text(encoding="utf-8") - before = "[tool][jupyter-packaging][external-data]" - after = "[tool][flit][external-data]" + before = "[tool.jupyter-packaging.external-data]" + after = "[tool.flit.external-data]" data = data.replace(before, after) pyproj_toml.write_text(data, encoding="utf-8")