From ca66db06efb1ca11327c27437c92de861b4aaa02 Mon Sep 17 00:00:00 2001 From: Marc Wouts Date: Thu, 23 Nov 2023 21:46:11 +0000 Subject: [PATCH 1/4] Move the jupytext-config helpers to a subpackage and add tests --- src/jupytext_config/__main__.py | 2 +- .../jupytext_config.py | 14 ++++-- .../labconfig.py | 23 +++++----- .../jupytext_config/test_jupytext_config.py | 17 +++++++ tests/unit/test_labconfig.py | 46 +++++++++++++++++++ 5 files changed, 87 insertions(+), 15 deletions(-) rename src/{jupytext => jupytext_config}/jupytext_config.py (83%) rename src/{jupytext => jupytext_config}/labconfig.py (83%) create mode 100644 tests/integration/jupytext_config/test_jupytext_config.py create mode 100644 tests/unit/test_labconfig.py diff --git a/src/jupytext_config/__main__.py b/src/jupytext_config/__main__.py index 5220474ac..a5538fe60 100644 --- a/src/jupytext_config/__main__.py +++ b/src/jupytext_config/__main__.py @@ -7,7 +7,7 @@ import sys -from jupytext.jupytext_config import main +from .jupytext_config import main if __name__ == "__main__": sys.exit(main()) diff --git a/src/jupytext/jupytext_config.py b/src/jupytext_config/jupytext_config.py similarity index 83% rename from src/jupytext/jupytext_config.py rename to src/jupytext_config/jupytext_config.py index 25ca5464f..a58750fd0 100644 --- a/src/jupytext/jupytext_config.py +++ b/src/jupytext_config/jupytext_config.py @@ -6,6 +6,7 @@ import sys from argparse import ArgumentParser +from pathlib import Path from .labconfig import LabConfig @@ -36,7 +37,7 @@ def __init__(self): ) def main(self, args): - LabConfig().read().list_default_viewer() + LabConfig(settings_path=args.settings_path).read().list_default_viewer() return 0 def fill_parser(self, subparser): @@ -48,7 +49,9 @@ def __init__(self): super().__init__("set-default-viewer", "Set default viewers for JupyterLab") def main(self, args): - LabConfig().read().set_default_viewers(args.doctype).write() + LabConfig(settings_path=args.settings_path).read().set_default_viewers( + args.doctype + ).write() return 0 def fill_parser(self, subparser): @@ -65,7 +68,9 @@ def __init__(self): super().__init__("unset-default-viewer", "Unset default viewers for JupyterLab") def main(self, args): - LabConfig().read().unset_default_viewers(args.doctype).write() + LabConfig(settings_path=args.settings_path).read().unset_default_viewers( + args.doctype + ).write() return 0 def fill_parser(self, subparser): @@ -87,6 +92,9 @@ def fill_parser(self, subparser): def main(): parser = ArgumentParser() + parser.add_argument( + "--settings-path", default=Path.home() / ".jupyter" / "labconfig" + ) subparsers = parser.add_subparsers(required=True) for subcommand in SUBCOMMANDS: subparser = subparsers.add_parser(subcommand.name, help=subcommand.help) diff --git a/src/jupytext/labconfig.py b/src/jupytext_config/labconfig.py similarity index 83% rename from src/jupytext/labconfig.py rename to src/jupytext_config/labconfig.py index 7c3d4fcf1..3f0e7eab8 100644 --- a/src/jupytext/labconfig.py +++ b/src/jupytext_config/labconfig.py @@ -14,8 +14,6 @@ class LabConfig: - SETTINGS = Path.home() / ".jupyter" / "labconfig" / "default_setting_overrides.json" - DOCTYPES = [ "python", "markdown", @@ -26,22 +24,25 @@ class LabConfig: "r", ] - def __init__(self, logger=None): + def __init__( + self, logger=None, settings_path=Path.home() / ".jupyter" / "labconfig" + ): self.logger = logger or logging.getLogger(__name__) self.config = {} # the state before any changes self._prior_config = {} + self.settings_file = Path(settings_path) / "default_setting_overrides.json" def read(self): """ read the labconfig settings file """ try: - if self.SETTINGS.exists(): - with self.SETTINGS.open() as fid: + if self.settings_file.exists(): + with self.settings_file.open() as fid: self.config = json.load(fid) except OSError as exc: - self.logger.error(f"Could not read {self.SETTINGS}", exc) + self.logger.error(f"Could not read {self.settings_file}", exc) return False # store for further comparison self._prior_config = copy.deepcopy(self.config) @@ -52,7 +53,7 @@ def list_default_viewer(self): list the current labconfig settings """ self.logger.debug( - f"Current @jupyterlab/docmanager-extension:plugin in {self.SETTINGS}" + f"Current @jupyterlab/docmanager-extension:plugin in {self.settings_file}" ) docmanager = self.config.get("@jupyterlab/docmanager-extension:plugin", {}) viewers = docmanager.get("defaultViewers", {}) @@ -103,17 +104,17 @@ def write(self) -> bool: """ # compare - avoid changing the file if nothing changed if self.config == self._prior_config: - self.logger.info(f"Nothing to do for {self.SETTINGS}") + self.logger.info(f"Nothing to do for {self.settings_file}") return True # save try: - self.SETTINGS.parent.mkdir(parents=True, exist_ok=True) - with self.SETTINGS.open("w") as fid: + self.settings_file.parent.mkdir(parents=True, exist_ok=True) + with self.settings_file.open("w") as fid: json.dump(self.config, fid, indent=2) # useful in case of successive write's self._prior_config = copy.deepcopy(self.config) return True except OSError as exc: - self.logger.error(f"Could not write {self.SETTINGS}", exc) + self.logger.error(f"Could not write {self.settings_file}", exc) return False diff --git a/tests/integration/jupytext_config/test_jupytext_config.py b/tests/integration/jupytext_config/test_jupytext_config.py new file mode 100644 index 000000000..99cf8dd74 --- /dev/null +++ b/tests/integration/jupytext_config/test_jupytext_config.py @@ -0,0 +1,17 @@ +from jupytext.cli import system + + +def test_jupytext_config_cli(tmp_path): + system("jupytext-config", "-h") + system( + "jupytext-config", + "--settings-path", + tmp_path, + "set-default-viewer", + "python", + "markdown", + ) + assert "python" in (tmp_path / "default_setting_overrides.json").read_text() + assert "markdown" in system( + "jupytext-config", "--settings-path", tmp_path, "list-default-viewer" + ) diff --git a/tests/unit/test_labconfig.py b/tests/unit/test_labconfig.py new file mode 100644 index 000000000..414aac8f4 --- /dev/null +++ b/tests/unit/test_labconfig.py @@ -0,0 +1,46 @@ +import json + +import pytest + +from jupytext_config.labconfig import LabConfig + + +@pytest.fixture() +def sample_lab_config(): + return { + "@jupyterlab/docmanager-extension:plugin": { + "defaultViewers": { + "markdown": "Jupytext Notebook", + "myst": "Jupytext Notebook", + "r-markdown": "Jupytext Notebook", + "quarto": "Jupytext Notebook", + "julia": "Jupytext Notebook", + "python": "Jupytext Notebook", + "r": "Jupytext Notebook", + } + } + } + + +def test_read_config(tmp_path, sample_lab_config): + (tmp_path / "default_setting_overrides.json").write_text( + json.dumps(sample_lab_config) + ) + labconfig = LabConfig(settings_path=tmp_path).read() + assert labconfig.config == sample_lab_config + + +def test_set_default_viewers(tmp_path, sample_lab_config): + labconfig = LabConfig(settings_path=tmp_path) + labconfig.set_default_viewers() + assert labconfig.config == sample_lab_config + + +def test_write_config(tmp_path, sample_lab_config): + labconfig = LabConfig(settings_path=tmp_path) + labconfig.set_default_viewers() + labconfig.write() + assert ( + json.loads((tmp_path / "default_setting_overrides.json").read_text()) + == sample_lab_config + ) From 90578170339a5b2b245b4b4010e07428cd961b2b Mon Sep 17 00:00:00 2001 From: Marc Wouts Date: Sat, 25 Nov 2023 17:01:23 +0000 Subject: [PATCH 2/4] Use settings_file rather than settings_path --- src/jupytext_config/jupytext_config.py | 13 ++++------ src/jupytext_config/labconfig.py | 10 ++++--- .../jupytext_config/test_jupytext_config.py | 9 ++++--- tests/unit/test_labconfig.py | 26 +++++++++---------- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/jupytext_config/jupytext_config.py b/src/jupytext_config/jupytext_config.py index a58750fd0..0cbfb4288 100644 --- a/src/jupytext_config/jupytext_config.py +++ b/src/jupytext_config/jupytext_config.py @@ -6,9 +6,8 @@ import sys from argparse import ArgumentParser -from pathlib import Path -from .labconfig import LabConfig +from .labconfig import DEFAULT_SETTINGS_FILE, LabConfig class SubCommand: @@ -37,7 +36,7 @@ def __init__(self): ) def main(self, args): - LabConfig(settings_path=args.settings_path).read().list_default_viewer() + LabConfig(settings_file=args.settings_file).read().list_default_viewer() return 0 def fill_parser(self, subparser): @@ -49,7 +48,7 @@ def __init__(self): super().__init__("set-default-viewer", "Set default viewers for JupyterLab") def main(self, args): - LabConfig(settings_path=args.settings_path).read().set_default_viewers( + LabConfig(settings_file=args.settings_file).read().set_default_viewers( args.doctype ).write() return 0 @@ -68,7 +67,7 @@ def __init__(self): super().__init__("unset-default-viewer", "Unset default viewers for JupyterLab") def main(self, args): - LabConfig(settings_path=args.settings_path).read().unset_default_viewers( + LabConfig(settings_file=args.settings_file).read().unset_default_viewers( args.doctype ).write() return 0 @@ -92,9 +91,7 @@ def fill_parser(self, subparser): def main(): parser = ArgumentParser() - parser.add_argument( - "--settings-path", default=Path.home() / ".jupyter" / "labconfig" - ) + parser.add_argument("--settings-file", default=DEFAULT_SETTINGS_FILE) subparsers = parser.add_subparsers(required=True) for subcommand in SUBCOMMANDS: subparser = subparsers.add_parser(subcommand.name, help=subcommand.help) diff --git a/src/jupytext_config/labconfig.py b/src/jupytext_config/labconfig.py index 3f0e7eab8..de4ddbcb8 100644 --- a/src/jupytext_config/labconfig.py +++ b/src/jupytext_config/labconfig.py @@ -12,6 +12,10 @@ # import pprint from pathlib import Path +DEFAULT_SETTINGS_FILE = ( + Path.home() / ".jupyter" / "labconfig" / "default_setting_overrides.json" +) + class LabConfig: DOCTYPES = [ @@ -24,14 +28,12 @@ class LabConfig: "r", ] - def __init__( - self, logger=None, settings_path=Path.home() / ".jupyter" / "labconfig" - ): + def __init__(self, logger=None, settings_file=DEFAULT_SETTINGS_FILE): self.logger = logger or logging.getLogger(__name__) self.config = {} # the state before any changes self._prior_config = {} - self.settings_file = Path(settings_path) / "default_setting_overrides.json" + self.settings_file = Path(settings_file) def read(self): """ diff --git a/tests/integration/jupytext_config/test_jupytext_config.py b/tests/integration/jupytext_config/test_jupytext_config.py index 99cf8dd74..6f82054cd 100644 --- a/tests/integration/jupytext_config/test_jupytext_config.py +++ b/tests/integration/jupytext_config/test_jupytext_config.py @@ -2,16 +2,17 @@ def test_jupytext_config_cli(tmp_path): + settings_file = tmp_path / "default_setting_overrides.json" system("jupytext-config", "-h") system( "jupytext-config", - "--settings-path", - tmp_path, + "--settings-file", + str(settings_file), "set-default-viewer", "python", "markdown", ) - assert "python" in (tmp_path / "default_setting_overrides.json").read_text() + assert "python" in (settings_file).read_text() assert "markdown" in system( - "jupytext-config", "--settings-path", tmp_path, "list-default-viewer" + "jupytext-config", "--settings-file", settings_file, "list-default-viewer" ) diff --git a/tests/unit/test_labconfig.py b/tests/unit/test_labconfig.py index 414aac8f4..c099ccca3 100644 --- a/tests/unit/test_labconfig.py +++ b/tests/unit/test_labconfig.py @@ -22,25 +22,25 @@ def sample_lab_config(): } -def test_read_config(tmp_path, sample_lab_config): - (tmp_path / "default_setting_overrides.json").write_text( - json.dumps(sample_lab_config) - ) - labconfig = LabConfig(settings_path=tmp_path).read() +@pytest.fixture() +def settings_file(tmp_path): + return tmp_path / "default_setting_overrides.json" + + +def test_read_config(settings_file, sample_lab_config): + (settings_file).write_text(json.dumps(sample_lab_config)) + labconfig = LabConfig(settings_file=settings_file).read() assert labconfig.config == sample_lab_config -def test_set_default_viewers(tmp_path, sample_lab_config): - labconfig = LabConfig(settings_path=tmp_path) +def test_set_default_viewers(settings_file, sample_lab_config): + labconfig = LabConfig(settings_file=settings_file) labconfig.set_default_viewers() assert labconfig.config == sample_lab_config -def test_write_config(tmp_path, sample_lab_config): - labconfig = LabConfig(settings_path=tmp_path) +def test_write_config(settings_file, sample_lab_config): + labconfig = LabConfig(settings_file=settings_file) labconfig.set_default_viewers() labconfig.write() - assert ( - json.loads((tmp_path / "default_setting_overrides.json").read_text()) - == sample_lab_config - ) + assert json.loads(settings_file.read_text()) == sample_lab_config From 59865fd4ce0bd1a6eccf70b033f39ee9ea8043ff Mon Sep 17 00:00:00 2001 From: Marc Wouts Date: Sat, 25 Nov 2023 20:24:04 +0000 Subject: [PATCH 3/4] Simplify LabConfig and increase the coverage --- src/jupytext_config/jupytext_config.py | 9 +-- src/jupytext_config/labconfig.py | 76 ++++++------------- .../jupytext_config/test_jupytext_config.py | 12 +++ tests/unit/test_labconfig.py | 25 ++++-- 4 files changed, 53 insertions(+), 69 deletions(-) diff --git a/src/jupytext_config/jupytext_config.py b/src/jupytext_config/jupytext_config.py index 0cbfb4288..451524c1e 100644 --- a/src/jupytext_config/jupytext_config.py +++ b/src/jupytext_config/jupytext_config.py @@ -23,17 +23,12 @@ def main(self, args): """ return 0 if all goes well """ - print( - f"{self.__class__.__name__}: redefine main() to implement this subcommand" - ) - return 1 + raise NotImplementedError() # pragma: no cover class ListDefaultViewer(SubCommand): def __init__(self): - super().__init__( - "list-default-viewer", "Display current settings in labconfig/" - ) + super().__init__("list-default-viewer", "Display current settings in labconfig") def main(self, args): LabConfig(settings_file=args.settings_file).read().list_default_viewer() diff --git a/src/jupytext_config/labconfig.py b/src/jupytext_config/labconfig.py index de4ddbcb8..69356422f 100644 --- a/src/jupytext_config/labconfig.py +++ b/src/jupytext_config/labconfig.py @@ -5,11 +5,8 @@ notebook will cause jupyterlab to open it in an editor, i.e. as a text file """ -import copy import json import logging - -# import pprint from pathlib import Path DEFAULT_SETTINGS_FILE = ( @@ -28,28 +25,28 @@ class LabConfig: "r", ] - def __init__(self, logger=None, settings_file=DEFAULT_SETTINGS_FILE): + def __init__(self, *, settings_file, logger=None): + self.settings_file = Path(settings_file) self.logger = logger or logging.getLogger(__name__) self.config = {} - # the state before any changes - self._prior_config = {} - self.settings_file = Path(settings_file) def read(self): """ read the labconfig settings file """ - try: - if self.settings_file.exists(): - with self.settings_file.open() as fid: - self.config = json.load(fid) - except OSError as exc: - self.logger.error(f"Could not read {self.settings_file}", exc) - return False - # store for further comparison - self._prior_config = copy.deepcopy(self.config) + if self.settings_file.exists(): + with self.settings_file.open() as fid: + self.config = json.load(fid) + else: + self.logger.info(f"Could not read from {self.settings_file} (not found)") + return self + def get_viewers(self): + return self.config.setdefault( + "@jupyterlab/docmanager-extension:plugin", {} + ).setdefault("defaultViewers", {}) + def list_default_viewer(self): """ list the current labconfig settings @@ -57,9 +54,7 @@ def list_default_viewer(self): self.logger.debug( f"Current @jupyterlab/docmanager-extension:plugin in {self.settings_file}" ) - docmanager = self.config.get("@jupyterlab/docmanager-extension:plugin", {}) - viewers = docmanager.get("defaultViewers", {}) - for key, value in viewers.items(): + for key, value in self.get_viewers().items(): print(f"{key}: {value}") def set_default_viewers(self, doctypes=None): @@ -70,18 +65,7 @@ def set_default_viewers(self, doctypes=None): return self def set_default_viewer(self, doctype): - if "@jupyterlab/docmanager-extension:plugin" not in self.config: - self.config["@jupyterlab/docmanager-extension:plugin"] = {} - if ( - "defaultViewers" - not in self.config["@jupyterlab/docmanager-extension:plugin"] - ): - self.config["@jupyterlab/docmanager-extension:plugin"][ - "defaultViewers" - ] = {} - viewers = self.config["@jupyterlab/docmanager-extension:plugin"][ - "defaultViewers" - ] + viewers = self.get_viewers() if doctype not in viewers: viewers[doctype] = "Jupytext Notebook" @@ -93,30 +77,14 @@ def unset_default_viewers(self, doctypes=None): return self def unset_default_viewer(self, doctype): - viewers = self.config.get("@jupyterlab/docmanager-extension:plugin", {}).get( - "defaultViewers", {} - ) - if doctype not in viewers: - return - del viewers[doctype] + viewers = self.get_viewers() + if doctype in viewers: + del viewers[doctype] - def write(self) -> bool: + def write(self): """ write the labconfig settings file """ - # compare - avoid changing the file if nothing changed - if self.config == self._prior_config: - self.logger.info(f"Nothing to do for {self.settings_file}") - return True - - # save - try: - self.settings_file.parent.mkdir(parents=True, exist_ok=True) - with self.settings_file.open("w") as fid: - json.dump(self.config, fid, indent=2) - # useful in case of successive write's - self._prior_config = copy.deepcopy(self.config) - return True - except OSError as exc: - self.logger.error(f"Could not write {self.settings_file}", exc) - return False + self.settings_file.parent.mkdir(parents=True, exist_ok=True) + with self.settings_file.open("w") as fid: + json.dump(self.config, fid, indent=2) diff --git a/tests/integration/jupytext_config/test_jupytext_config.py b/tests/integration/jupytext_config/test_jupytext_config.py index 6f82054cd..9b54cc6ec 100644 --- a/tests/integration/jupytext_config/test_jupytext_config.py +++ b/tests/integration/jupytext_config/test_jupytext_config.py @@ -16,3 +16,15 @@ def test_jupytext_config_cli(tmp_path): assert "markdown" in system( "jupytext-config", "--settings-file", settings_file, "list-default-viewer" ) + system( + "jupytext-config", + "--settings-file", + str(settings_file), + "unset-default-viewer", + "markdown", + ) + list_viewers = system( + "jupytext-config", "--settings-file", settings_file, "list-default-viewer" + ) + assert "python" in list_viewers + assert "markdown" not in list_viewers diff --git a/tests/unit/test_labconfig.py b/tests/unit/test_labconfig.py index c099ccca3..e96f9f326 100644 --- a/tests/unit/test_labconfig.py +++ b/tests/unit/test_labconfig.py @@ -6,7 +6,7 @@ @pytest.fixture() -def sample_lab_config(): +def sample_viewer_config(): return { "@jupyterlab/docmanager-extension:plugin": { "defaultViewers": { @@ -22,25 +22,34 @@ def sample_lab_config(): } +@pytest.fixture() +def sample_empty_viewer_config(): + return {"@jupyterlab/docmanager-extension:plugin": {"defaultViewers": {}}} + + @pytest.fixture() def settings_file(tmp_path): return tmp_path / "default_setting_overrides.json" -def test_read_config(settings_file, sample_lab_config): - (settings_file).write_text(json.dumps(sample_lab_config)) +def test_read_config(settings_file, sample_viewer_config): + (settings_file).write_text(json.dumps(sample_viewer_config)) labconfig = LabConfig(settings_file=settings_file).read() - assert labconfig.config == sample_lab_config + assert labconfig.config == sample_viewer_config -def test_set_default_viewers(settings_file, sample_lab_config): +def test_set_unset_default_viewers( + settings_file, sample_viewer_config, sample_empty_viewer_config +): labconfig = LabConfig(settings_file=settings_file) labconfig.set_default_viewers() - assert labconfig.config == sample_lab_config + assert labconfig.config == sample_viewer_config + labconfig.unset_default_viewers() + assert labconfig.config == sample_empty_viewer_config -def test_write_config(settings_file, sample_lab_config): +def test_write_config(settings_file, sample_viewer_config): labconfig = LabConfig(settings_file=settings_file) labconfig.set_default_viewers() labconfig.write() - assert json.loads(settings_file.read_text()) == sample_lab_config + assert json.loads(settings_file.read_text()) == sample_viewer_config From 15452de394a4fdeb0ffd27d23d20a8064224df99 Mon Sep 17 00:00:00 2001 From: Marc Wouts Date: Sat, 25 Nov 2023 20:28:28 +0000 Subject: [PATCH 4/4] Locate the config using jupyter_core.paths.jupyter_config_dir() --- src/jupytext_config/jupytext_config.py | 12 ++++++++++-- src/jupytext_config/labconfig.py | 4 ---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/jupytext_config/jupytext_config.py b/src/jupytext_config/jupytext_config.py index 451524c1e..871395e33 100644 --- a/src/jupytext_config/jupytext_config.py +++ b/src/jupytext_config/jupytext_config.py @@ -6,8 +6,11 @@ import sys from argparse import ArgumentParser +from pathlib import Path -from .labconfig import DEFAULT_SETTINGS_FILE, LabConfig +import jupyter_core.paths as jupyter_core_paths + +from .labconfig import LabConfig class SubCommand: @@ -86,7 +89,12 @@ def fill_parser(self, subparser): def main(): parser = ArgumentParser() - parser.add_argument("--settings-file", default=DEFAULT_SETTINGS_FILE) + parser.add_argument( + "--settings-file", + default=Path(jupyter_core_paths.jupyter_config_dir()) + / "labconfig" + / "default_setting_overrides.json", + ) subparsers = parser.add_subparsers(required=True) for subcommand in SUBCOMMANDS: subparser = subparsers.add_parser(subcommand.name, help=subcommand.help) diff --git a/src/jupytext_config/labconfig.py b/src/jupytext_config/labconfig.py index 69356422f..05c3ac2da 100644 --- a/src/jupytext_config/labconfig.py +++ b/src/jupytext_config/labconfig.py @@ -9,10 +9,6 @@ import logging from pathlib import Path -DEFAULT_SETTINGS_FILE = ( - Path.home() / ".jupyter" / "labconfig" / "default_setting_overrides.json" -) - class LabConfig: DOCTYPES = [